diff --git a/.github/workflows/bokeh-ci.yml b/.github/workflows/bokeh-ci.yml index b54644ac229..2ab01c49881 100644 --- a/.github/workflows/bokeh-ci.yml +++ b/.github/workflows/bokeh-ci.yml @@ -179,36 +179,6 @@ jobs: name: examples-report path: examples-report - integration-tests: - if: ${{ false }} # disable for now - needs: build - runs-on: ubuntu-latest - - steps: - - uses: actions/checkout@v4 - - - name: Prepare Environment - uses: ./.github/workflows/composite/test-setup - with: - test-env: '3.11' - source-tree: 'delete' - - - name: List installed software - run: | - conda info - conda list - echo "node $(node --version)" - echo "npm $(npm --version)" - - - name: Run tests - run: pytest -v --cov=bokeh --cov-report=xml --tb=short --driver chrome --color=yes tests/integration - - - name: Upload code coverage - uses: codecov/codecov-action@v4 - with: - flags: integration - verbose: true - unit-test: needs: build runs-on: ${{ matrix.os }} @@ -351,27 +321,3 @@ jobs: - name: Run tests run: bash scripts/ci/run_downstream_tests.sh - - docker_from_wheel: - if: ${{ false }} # temporarily disable - needs: build - runs-on: ubuntu-latest - env: - IMAGE_TAG: bokeh/bokeh-dev:branch-3.1 - - steps: - - uses: actions/checkout@v4 - - - name: Download wheel package - id: download - uses: actions/download-artifact@v4 - with: - name: wheel-package - path: dist/ - - - name: Start Docker container, install Bokeh from wheel and run Python tests. - env: - BOKEH_DOCKER_FROM_WHEEL: 1 - BOKEH_DOCKER_INTERACTIVE: 0 - run: | - scripts/docker/docker_run.sh $IMAGE_TAG diff --git a/.github/workflows/bokeh-docker-build.yml b/.github/workflows/bokeh-docker-build.yml deleted file mode 100644 index 70f3502fb18..00000000000 --- a/.github/workflows/bokeh-docker-build.yml +++ /dev/null @@ -1,72 +0,0 @@ -name: Bokeh-Docker-Build - -on: - workflow_dispatch: - inputs: - push_or_save_image: - type: choice - description: What to do with image? - options: - - "Push to Docker Hub" - - "Save as artifact" - required: true - default: "Push to Docker Hub" - -jobs: - docker-build: - runs-on: ubuntu-latest - steps: - - name: Set date environment variable - run: | - echo "iso_date=$(date -u --iso-8601)" >> $GITHUB_ENV - echo "branch_name=$(echo ${{ github.ref_name }} | tr / -)" >> $GITHUB_ENV - - - name: Checkout source - uses: actions/checkout@v4 - with: - fetch-depth: 0 - - - name: Set up QEMU - uses: docker/setup-qemu-action@v2 - - - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v2 - - - name: Login to Docker Hub - uses: docker/login-action@v2 - with: - username: bokehservice - password: ${{ secrets.DOCKER_TOKEN }} - - - name: Build image - uses: docker/build-push-action@v5 - with: - context: scripts/docker - load: true - tags: | - bokeh/bokeh-dev:latest - bokeh/bokeh-dev:${{ env.iso_date }} - bokeh/bokeh-dev:${{ env.branch_name }} - - - name: Push image to Docker Hub - if: ${{ inputs.push_or_save_image }} == "Push to Docker Hub" - uses: docker/build-push-action@v5 - with: - context: scripts/docker - push: true - tags: | - bokeh/bokeh-dev:latest - bokeh/bokeh-dev:${{ env.iso_date }} - bokeh/bokeh-dev:${{ env.branch_name }} - - - name: Save image to tar file - if: ${{ inputs.push_to_docker_hub }} == "Save as artifact" - run: | - docker save -o bokeh-dev.tar bokeh/bokeh-dev - - - name: Upload artifact - if: ${{ inputs.push_to_docker_hub }} == "Save as artifact" - uses: actions/upload-artifact@v4 - with: - name: artifact - path: bokeh-dev.tar diff --git a/.github/workflows/bokeh-docker-test.yml b/.github/workflows/bokeh-docker-test.yml deleted file mode 100644 index 820c6da92fb..00000000000 --- a/.github/workflows/bokeh-docker-test.yml +++ /dev/null @@ -1,48 +0,0 @@ -name: Bokeh-Docker-Test - -on: - push: - branches: - - main - - branch-* - pull_request: - -jobs: - docker-test: - if: ${{ false }} # disable for now - - runs-on: ubuntu-latest - env: - IMAGE_TAG: bokeh/bokeh-dev:branch-3.1 - - steps: - - name: Checkout the repository - uses: actions/checkout@v4 - with: - fetch-depth: 0 # full history to get proper build version - - - name: Start Docker container, build Bokeh and run tests. - env: - BOKEH_DOCKER_BUILD: 1 - BOKEH_DOCKER_TEST: 1 - BOKEH_DOCKER_INTERACTIVE: 0 - run: | - scripts/docker/docker_run.sh $IMAGE_TAG - - - name: Collect results - shell: bash - run: | - SRC="bokehjs/test/baselines/linux" - DST="bokeh-report-docker/${SRC}" - mkdir -p ${DST} - if [[ -e ${SRC}/report.json ]]; - then - CHANGED=$(git status --short ${SRC}/\*.blf ${SRC}/\*.png | cut -c4-) - cp ${SRC}/report.json ${CHANGED} ${DST} - fi - - - name: Upload report - uses: actions/upload-artifact@v4 - with: - name: bokeh-report-docker - path: bokeh-report-docker diff --git a/scripts/ci/install_downstream_packages.sh b/scripts/ci/install_downstream_packages.sh index e153c82024c..b01b26fa081 100644 --- a/scripts/ci/install_downstream_packages.sh +++ b/scripts/ci/install_downstream_packages.sh @@ -30,8 +30,3 @@ banner "dask/dask" 2> /dev/null pip install pytest-timeout pytest-cov pytest-rerunfailures pytest-repeat git clone https://github.com/dask/dask.git pip install -e "./dask[test]" # "test" extra installs additional testing dependencies - -banner "pandas_bokeh" 2> /dev/null -pip install pandas_bokeh -pip install geopandas -git clone https://github.com/PatrikHlobil/Pandas-Bokeh.git diff --git a/scripts/ci/run_downstream_tests.sh b/scripts/ci/run_downstream_tests.sh index 7b620edc060..fbfab0d30cb 100644 --- a/scripts/ci/run_downstream_tests.sh +++ b/scripts/ci/run_downstream_tests.sh @@ -39,12 +39,6 @@ pytest panel/tests banner "Holoviews" 2> /dev/null nosetests holoviews/tests/plotting/bokeh -popd || exit - -banner "PandasBokeh" 2> /dev/null -pytest Pandas-Bokeh/Tests/test_PandasBokeh.py - -banner "GeoPandasBokeh" 2> /dev/null -pytest Pandas-Bokeh/Tests/test_GeoPandasBokeh.py +popd exit 0 diff --git a/scripts/docker/Dockerfile b/scripts/docker/Dockerfile deleted file mode 100644 index 91b74a2d3c7..00000000000 --- a/scripts/docker/Dockerfile +++ /dev/null @@ -1,49 +0,0 @@ -FROM ubuntu:22.04 - -ARG CHROME_DEB=google-chrome-stable_current_amd64.deb -ARG FIXUID_VERSION=0.5.1 - -ENV DEBIAN_FRONTEND=noninteractive - -# fonts-dejavu-core needed for headless chrome unicode characters. -RUN apt update -y && \ - apt upgrade -y && \ - apt install -y curl fonts-dejavu-core git sudo unzip - -# User and group setup using fixuid. -RUN addgroup --gid 1000 docker && \ - adduser --uid 1000 --ingroup docker --home /home/docker --shell /bin/bash --disabled-password --gecos "" docker && \ - USER=docker && \ - GROUP=docker && \ - ARCH="$(dpkg --print-architecture)" && \ - curl -fsSL "https://github.com/boxboat/fixuid/releases/download/v$FIXUID_VERSION/fixuid-$FIXUID_VERSION-linux-$ARCH.tar.gz" | tar -C /usr/local/bin -xzf - && \ - chown root:root /usr/local/bin/fixuid && \ - chmod 4755 /usr/local/bin/fixuid && \ - mkdir -p /etc/fixuid && \ - printf "user: $USER\ngroup: $GROUP\n" > /etc/fixuid/config.yml - -# Cannot 'snap install chromium' in docker container, so instead install google-chrome deb. -RUN curl -LO https://dl.google.com/linux/direct/$CHROME_DEB && \ - apt install --no-install-recommends -y ./$CHROME_DEB && \ - rm $CHROME_DEB && \ - apt autoremove -y && \ - apt clean -y && \ - rm -rf /var/lib/apt/lists/* - -# Download correct version of chromedriver and put in path. -RUN CHROME_VERSION=$(google-chrome --version | awk '{print $NF}' | sed 's/\.[0-9]\+$//g') && \ - DRIVER_VERSION=$(curl https://chromedriver.storage.googleapis.com/LATEST_RELEASE_$CHROME_VERSION) && \ - curl -LO https://chromedriver.storage.googleapis.com/$DRIVER_VERSION/chromedriver_linux64.zip && \ - unzip chromedriver_linux64.zip && \ - mv chromedriver /usr/bin && \ - rm chromedriver_linux64.zip - -EXPOSE 5006 - -COPY entrypoint.sh /usr/bin/entrypoint.sh -RUN chmod a+x /usr/bin/entrypoint.sh -ENTRYPOINT ["/usr/bin/entrypoint.sh"] - -ENV BOKEH_IN_DOCKER=1 -USER docker:docker -WORKDIR /bokeh diff --git a/scripts/docker/bokeh_docker_build.sh b/scripts/docker/bokeh_docker_build.sh deleted file mode 100644 index d6ed0bbea03..00000000000 --- a/scripts/docker/bokeh_docker_build.sh +++ /dev/null @@ -1,12 +0,0 @@ -#!/bin/bash - -set -eu - -echo "Start of $0" - -bash scripts/ci/install_node_modules.sh -pip install -ve . - -python -m bokeh info - -echo "End of $0" diff --git a/scripts/docker/bokeh_docker_from_wheel.sh b/scripts/docker/bokeh_docker_from_wheel.sh deleted file mode 100644 index 13fcb61d2fe..00000000000 --- a/scripts/docker/bokeh_docker_from_wheel.sh +++ /dev/null @@ -1,18 +0,0 @@ -#!/bin/bash -# This installs Bokeh from a single wheel in the dist directory and runs some of the Python tests. - -set -eu - -echo "Start of $0" - -pip install dist/bokeh*.whl -bash scripts/ci/install_node_modules.sh - -python -m bokeh info - -# Bokeh Python tests. -pytest tests/test_defaults.py -pytest tests/unit -k "not firefox" -#pytest tests/integration - -echo "End of $0" diff --git a/scripts/docker/bokeh_docker_test.sh b/scripts/docker/bokeh_docker_test.sh deleted file mode 100644 index 5a5c14062db..00000000000 --- a/scripts/docker/bokeh_docker_test.sh +++ /dev/null @@ -1,14 +0,0 @@ -#!/bin/bash - -# Note do not exit on error, as want remaining tests to run. -set -u - -echo "Start of $0" - -python -m bokeh info - -# Run some of the tests. -pytest tests/codebase -cd bokehjs && node make test - -echo "End of $0" diff --git a/scripts/docker/docker_run.sh b/scripts/docker/docker_run.sh deleted file mode 100755 index 383dc4da062..00000000000 --- a/scripts/docker/docker_run.sh +++ /dev/null @@ -1,36 +0,0 @@ -#!/bin/bash - -# Usage: docker_run.sh bokeh-dev:latest -# Can put env vars first, e.g. -# BOKEH_DOCKER_PY=3.10 docker_run.sh bokeh-dev:latest - -set -eu - -if [ $# -ne 1 ]; then - echo "Usage: docker_run.sh , e.g. docker_run.sh bokeh/bokeh-dev:latest" - exit 1 -fi - -IMAGE_AND_TAG=$1 -UID_GID="`id -u`:`id -g`" - -# Environment variables that are passed in to Docker container. -ENV_VARS="" -for name in BOKEH_DOCKER_CONDA BOKEH_DOCKER_PY BOKEH_DOCKER_BUILD BOKEH_DOCKER_TEST BOKEH_DOCKER_CHROME_VERSION BOKEH_DOCKER_FROM_WHEEL; do - if [ -n "${!name+set}" ]; then - ENV_VARS="$ENV_VARS -e $name=${!name}" - fi -done - -INTERACTIVE="-it" -if [ "${BOKEH_DOCKER_INTERACTIVE:-1}" == 0 ]; then - INTERACTIVE="" -fi -if [ "${BOKEH_DOCKER_CHROME_VERSION:-0}" == 1 ]; then - # If only want chrome version, do not need to run interactively. - INTERACTIVE="" -fi - -CMD="docker run -v $PWD:/bokeh -u $UID_GID -p 5006:5006 $ENV_VARS $INTERACTIVE $IMAGE_AND_TAG" -echo $CMD -$CMD diff --git a/scripts/docker/entrypoint.sh b/scripts/docker/entrypoint.sh deleted file mode 100644 index 37215e621f3..00000000000 --- a/scripts/docker/entrypoint.sh +++ /dev/null @@ -1,70 +0,0 @@ -#!/bin/bash - -set -eu - -CONDA_DIR=/home/docker/conda_bokeh -DEFAULT_PY=3.10 -ENV_NAME=bkdev -MINICONDA_SCRIPT=Miniconda3-latest-Linux-x86_64.sh - -eval "$(fixuid -q)" - -if [ "${BOKEH_DOCKER_CHROME_VERSION:-0}" == 1 ]; then - # Print numerical chrome version and exit. - google-chrome --version | awk '{print $NF}' - exit 1 -fi - -if [ ! -d .git ] || [ ! -f conda/environment-test-3.10.yml ]; then - echo "Directory does not contain Bokeh git repo." - exit 2 -fi - -if [ "${BOKEH_DOCKER_CONDA:-1}" == 1 ]; then - # Check environment file exists. - BOKEH_DOCKER_PY=${BOKEH_DOCKER_PY:-$DEFAULT_PY} - ENV_YML_FILE=conda/environment-test-$BOKEH_DOCKER_PY.yml - if [ ! -f $ENV_YML_FILE ]; then - echo "Cannot find environment file $ENV_YML_FILE" - exit 3 - fi - - if [ ! -f "$CONDA_DIR/condabin/conda" ]; then - # Install miniconda into $CONDA_DIR on docker filesystem. - START_DIR=$(pwd) - cd /tmp - curl -LO "http://repo.continuum.io/miniconda/$MINICONDA_SCRIPT" - bash $MINICONDA_SCRIPT -p $CONDA_DIR -b - rm $MINICONDA_SCRIPT - cd $START_DIR - fi - - # Activate conda in .bashrc and in this shell. - $CONDA_DIR/condabin/conda init bash > /dev/null - . $CONDA_DIR/etc/profile.d/conda.sh - - if [ ! -d "$CONDA_DIR/envs/$ENV_NAME" ]; then - # Create conda environment and install required packagaes. - conda env create -n $ENV_NAME -f $ENV_YML_FILE - fi - - # Ensure conda environment is activated in this shell and in new shells. - conda activate $ENV_NAME - echo "conda activate $ENV_NAME" >> ~/.bashrc -fi - -google-chrome --version - -if [ "${BOKEH_DOCKER_BUILD:-0}" == 1 ]; then - bash scripts/docker/bokeh_docker_build.sh -fi - -if [ "${BOKEH_DOCKER_TEST:-0}" == 1 ]; then - bash scripts/docker/bokeh_docker_test.sh -fi - -if [ "${BOKEH_DOCKER_FROM_WHEEL:-0}" == 1 ]; then - bash scripts/docker/bokeh_docker_from_wheel.sh -fi - -/bin/bash "$@" diff --git a/tests/integration/__init__.py b/tests/integration/__init__.py deleted file mode 100644 index c8c348f1b36..00000000000 --- a/tests/integration/__init__.py +++ /dev/null @@ -1,7 +0,0 @@ -#----------------------------------------------------------------------------- -# Copyright (c) Anaconda, Inc. All rights reserved. -# -# Powered by the Bokeh Development Team. -# -# The full license is in the file LICENSE.txt, distributed with this software. -#----------------------------------------------------------------------------- diff --git a/tests/integration/embed/__init__.py b/tests/integration/embed/__init__.py deleted file mode 100644 index c8c348f1b36..00000000000 --- a/tests/integration/embed/__init__.py +++ /dev/null @@ -1,7 +0,0 @@ -#----------------------------------------------------------------------------- -# Copyright (c) Anaconda, Inc. All rights reserved. -# -# Powered by the Bokeh Development Team. -# -# The full license is in the file LICENSE.txt, distributed with this software. -#----------------------------------------------------------------------------- diff --git a/tests/integration/embed/test_json_item.py b/tests/integration/embed/test_json_item.py deleted file mode 100644 index 08dd8241a21..00000000000 --- a/tests/integration/embed/test_json_item.py +++ /dev/null @@ -1,71 +0,0 @@ -#----------------------------------------------------------------------------- -# Copyright (c) Anaconda, Inc. All rights reserved. -# -# Powered by the Bokeh Development Team. -# -# The full license is in the file LICENSE.txt, distributed with this software. -#----------------------------------------------------------------------------- - -#----------------------------------------------------------------------------- -# Boilerplate -#----------------------------------------------------------------------------- -from __future__ import annotations # isort:skip - -import pytest ; pytest - -#----------------------------------------------------------------------------- -# Imports -#----------------------------------------------------------------------------- - -# Standard library imports -import json - -# External imports -from jinja2 import Template -from selenium.webdriver.common.by import By - -# Bokeh imports -from bokeh.embed import json_item -from bokeh.models import Plot -from bokeh.resources import INLINE - -#----------------------------------------------------------------------------- -# Tests -#----------------------------------------------------------------------------- - -pytest_plugins = ( - "tests.support.plugins.project", - "tests.support.plugins.selenium", -) - -PAGE = Template(""" - - - - {{ resources }} - - - -
- - -""") - - -@pytest.mark.selenium -class Test_json_item: - def test_bkroot_added_to_target(self, driver, test_file_path_and_url, has_no_console_errors) -> None: - p = Plot(css_classes=["this-plot"]) - html = PAGE.render(item=json.dumps(json_item(p)), resources=INLINE.render()) - - path, url = test_file_path_and_url - with open(path, "w") as f: - f.write(html) - - driver.get(url) - - div = driver.find_elements(By.CLASS_NAME, "this-plot") - assert has_no_console_errors(driver) - assert len(div) == 1 diff --git a/tests/integration/models/__init__.py b/tests/integration/models/__init__.py deleted file mode 100644 index e69de29bb2d..00000000000 diff --git a/tests/integration/models/test_datarange1d.py b/tests/integration/models/test_datarange1d.py deleted file mode 100644 index b654103627c..00000000000 --- a/tests/integration/models/test_datarange1d.py +++ /dev/null @@ -1,123 +0,0 @@ -#----------------------------------------------------------------------------- -# Copyright (c) Anaconda, Inc. All rights reserved. -# -# Powered by the Bokeh Development Team. -# -# The full license is in the file LICENSE.txt, distributed with this software. -#----------------------------------------------------------------------------- - -#----------------------------------------------------------------------------- -# Boilerplate -#----------------------------------------------------------------------------- -from __future__ import annotations # isort:skip - -import pytest ; pytest - -#----------------------------------------------------------------------------- -# Imports -#----------------------------------------------------------------------------- - -# Bokeh imports -from bokeh.layouts import column -from bokeh.models import ( - Button, - Circle, - ColumnDataSource, - CustomJS, - DataRange1d, - Plot, -) -from tests.support.plugins.project import SinglePlotPage -from tests.support.util.selenium import RECORD, find_element_for - -#----------------------------------------------------------------------------- -# Tests -#----------------------------------------------------------------------------- - -pytest_plugins = ( - "tests.support.plugins.project", -) - -def _make_plot(**kw): - source = ColumnDataSource(dict(x=[1, 2], y1=[0, 1], y2=[10,11])) - plot = Plot(height=400, width=400, x_range=DataRange1d(), y_range=DataRange1d(**kw), min_border=0) - plot.add_glyph(source, Circle(x='x', y='y1')) - glyph = plot.add_glyph(source, Circle(x='x', y='y2')) - glyph.visible = False - code = RECORD("yrstart", "p.y_range.start", final=False) + RECORD("yrend", "p.y_range.end") - plot.tags.append(CustomJS(name="custom-action", args=dict(p=plot), code=code)) - plot.toolbar_sticky = False - return plot, glyph - - -@pytest.mark.selenium -class Test_DataRange1d: - def test_includes_hidden_glyphs_by_default(self, single_plot_page: SinglePlotPage) -> None: - plot, glyph = _make_plot() - - page = single_plot_page(plot) - - page.eval_custom_action() - - results = page.results - assert results['yrstart'] <= 0 - assert results['yrend'] >= 11 - - assert page.has_no_console_errors() - - def test_includes_hidden_glyphs_when_asked(self, single_plot_page: SinglePlotPage) -> None: - plot, glyph = _make_plot(only_visible=False) - - page = single_plot_page(plot) - - page.eval_custom_action() - - results = page.results - assert results['yrstart'] <= 0 - assert results['yrend'] >= 11 - - assert page.has_no_console_errors() - - def test_excludes_hidden_glyphs_when_asked(self, single_plot_page: SinglePlotPage) -> None: - plot, glyph = _make_plot(only_visible=True) - - page = single_plot_page(plot) - - page.eval_custom_action() - - results = page.results - assert results['yrstart'] <= 0 - assert results['yrend'] < 5 - - assert page.has_no_console_errors() - - - def test_updates_when_visibility_is_toggled(self, single_plot_page: SinglePlotPage) -> None: - source = ColumnDataSource(dict(x=[1, 2], y1=[0, 1], y2=[10,11])) - plot = Plot(height=400, width=400, x_range=DataRange1d(), y_range=DataRange1d(only_visible=True), min_border=0) - plot.add_glyph(source, Circle(x='x', y='y1')) - glyph = plot.add_glyph(source, Circle(x='x', y='y2')) - code = RECORD("yrstart", "p.y_range.start", final=False) + RECORD("yrend", "p.y_range.end") - plot.tags.append(CustomJS(name="custom-action", args=dict(p=plot), code=code)) - plot.toolbar_sticky = False - button = Button() - button.js_on_event('button_click', CustomJS(args=dict(glyph=glyph), code="glyph.visible=false")) - - page = single_plot_page(column(plot, button)) - - page.eval_custom_action() - - results = page.results - assert results['yrstart'] <= 0 - assert results['yrend'] >= 11 - - button = find_element_for(page.driver, button, ".bk-btn") - button.click() - - page.eval_custom_action() - - results = page.results - assert results['yrstart'] <= 0 - assert results['yrend'] < 5 - - assert page.has_no_console_errors() diff --git a/tests/integration/models/test_plot.py b/tests/integration/models/test_plot.py deleted file mode 100644 index f0ecc642b50..00000000000 --- a/tests/integration/models/test_plot.py +++ /dev/null @@ -1,164 +0,0 @@ -#----------------------------------------------------------------------------- -# Copyright (c) Anaconda, Inc. All rights reserved. -# -# Powered by the Bokeh Development Team. -# -# The full license is in the file LICENSE.txt, distributed with this software. -#----------------------------------------------------------------------------- - -#----------------------------------------------------------------------------- -# Boilerplate -#----------------------------------------------------------------------------- -from __future__ import annotations # isort:skip - -import pytest ; pytest - -#----------------------------------------------------------------------------- -# Imports -#----------------------------------------------------------------------------- - -# Standard library imports -import time -from dataclasses import dataclass -from typing import Any - -# Bokeh imports -from bokeh.core.property.singletons import Undefined -from bokeh.document import Document -from bokeh.events import LODEnd, LODStart, RangesUpdate -from bokeh.layouts import column -from bokeh.models import Button, Plot, Range1d -from bokeh.plotting import figure -from bokeh.plotting.glyph_api import GlyphAPI -from tests.support.plugins.project import BokehServerPage -from tests.support.util.selenium import find_element_for - -#----------------------------------------------------------------------------- -# Tests -#----------------------------------------------------------------------------- - -pytest_plugins = ( - "tests.support.plugins.project", -) - -# TODO: generalize this -class Fig(Plot, GlyphAPI): - """ A plot with glyph API methods, e.g. ``fig.scatter(x, y, size=10)``. """ - __data_model__ = True - - @property - def plot(self): - return self - - @property - def coordinates(self): - return None - -@pytest.mark.selenium -class Test_Plot: - def test_inner_dims_trigger_on_dynamic_add(self, bokeh_server_page: BokehServerPage) -> None: - button = Button() - - @dataclass - class Data: - iw: tuple[int, Any] | None = None - ih: tuple[int, Any] | None = None - - data = Data() - - def modify_doc(doc: Document): - p1 = Fig(height=400, width=400, x_range=Range1d(0, 1), y_range=Range1d(0, 1), min_border=10) - p1.circle([1, 2, 3], [1, 2, 3]) - p2 = Fig(height=400, width=400, x_range=Range1d(0, 1), y_range=Range1d(0, 1), min_border=10) - p1.circle([1, 2, 3], [1, 2, 3]) - - layout = column(p1, button) - def cb(): - if p2 not in layout.children: - layout.children = [p1, button, p2] - button.on_event('button_click', cb) - def iw(attr: str, old: int, new: int): data.iw = (old, new) - def ih(attr: str, old: int, new: int): data.ih = (old, new) - p2.on_change('inner_width', iw) - p2.on_change('inner_height', ih) - doc.add_root(layout) - - page = bokeh_server_page(modify_doc) - - find_element_for(page.driver, button, ".bk-btn").click() - - # updates can take some time - time.sleep(0.5) - - assert data.iw is not None, "inner_width was not updated" - assert data.iw[0] is Undefined - assert isinstance(data.iw[1], int) - assert 0 < data.iw[1] < 400 - - assert data.ih is not None, "inner_height was not updated" - assert data.ih[0] is Undefined - assert isinstance(data.ih[1], int) - assert 0 < data.ih[1] < 400 - - assert page.has_no_console_errors() - - def test_lod_event_triggering(self, bokeh_server_page: BokehServerPage) -> None: - good_events: list[str] = [] - bad_events: list[str] = [] - - x_range = Range1d(0, 4) - y_range = Range1d(0, 4) - p1 = figure(height=400, width=400, x_range=x_range, y_range=y_range, lod_interval=200, lod_timeout=300) - p1.line([1, 2, 3], [1, 2, 3]) - p2 = figure(height=400, width=400, x_range=x_range, y_range=y_range, lod_interval=200, lod_timeout=300) - p2.line([1, 2, 3], [1, 2, 3]) - - def modify_doc(doc: Document): - p1.on_event(LODStart, lambda: good_events.append("LODStart")) - p1.on_event(LODEnd, lambda: good_events.append("LODEnd")) - # These 2 should not fire, pan is on p1 - p2.on_event(LODStart, lambda: bad_events.append("LODStart")) - p2.on_event(LODEnd, lambda: bad_events.append("LODEnd")) - - layout = column(p1, p2) - doc.add_root(layout) - - page = bokeh_server_page(modify_doc) - - # This can only be called once - calling it multiple times appears to have no effect - page.drag_canvas_at_position(p1, 100, 100, 200, 200) - - # Wait for drag to happen - time.sleep(0.1) - assert good_events == ["LODStart"] - assert bad_events == [] - - # Wait for lod_timeout to hit - time.sleep(0.3) - assert good_events == ["LODStart", "LODEnd"] - assert bad_events == [] - - def test_ranges_update_event_trigger_on_pan(self, bokeh_server_page: BokehServerPage) -> None: - events = [] - - x_range = Range1d(0, 4) - y_range = Range1d(0, 4) - p = figure(height=400, width=400, x_range=x_range, y_range=y_range) - p.line([1, 2, 3], [1, 2, 3]) - - def modify_doc(doc: Document): - p.on_event(RangesUpdate, lambda evt: events.append(("RangesUpdate", evt.x0, evt.x1, evt.y0, evt.y1))) - doc.add_root(p) - - page = bokeh_server_page(modify_doc) - - # This can only be called once - calling it multiple times appears to have no effect - page.drag_canvas_at_position(p, 100, 100, 200, 200) - - # Wait for drag to happen - time.sleep(0.2) - assert events[0][0] == "RangesUpdate" - assert events[0][1] < -2.3 - assert events[0][2] < 1.7 - assert events[0][3] > 2.1 - assert events[0][4] > 6.1 diff --git a/tests/integration/models/test_sources.py b/tests/integration/models/test_sources.py deleted file mode 100644 index 35aa5f3e7ba..00000000000 --- a/tests/integration/models/test_sources.py +++ /dev/null @@ -1,139 +0,0 @@ -#----------------------------------------------------------------------------- -# Copyright (c) Anaconda, Inc. All rights reserved. -# -# Powered by the Bokeh Development Team. -# -# The full license is in the file LICENSE.txt, distributed with this software. -#----------------------------------------------------------------------------- - -#----------------------------------------------------------------------------- -# Boilerplate -#----------------------------------------------------------------------------- -from __future__ import annotations # isort:skip - -import pytest ; pytest - -#----------------------------------------------------------------------------- -# Imports -#----------------------------------------------------------------------------- - -# Bokeh imports -from bokeh.layouts import column -from bokeh.models import ( - Button, - ColumnDataSource, - CustomJS, - Plot, - Range1d, -) -from tests.support.plugins.project import BokehServerPage -from tests.support.util.selenium import RECORD, find_element_for - -#----------------------------------------------------------------------------- -# Tests -#----------------------------------------------------------------------------- - -pytest_plugins = ( - "tests.support.plugins.project", -) - -def is_cds_data_changed(evt): - return evt['kind'] == 'ModelChanged' and evt['attr'] == 'data' - -def is_cds_data_patched(evt): - return evt['kind'] == 'ColumnsPatched' - -def is_cds_data_streamed(evt): - return evt['kind'] == 'ColumnsStreamed' - - - -@pytest.mark.selenium -class Test_ColumnDataSource: - def test_client_source_patch_sends_patch_event(self, bokeh_server_page: BokehServerPage) -> None: - data = {'x': [1,2,3,4], 'y': [10,20,30,40]} - source = ColumnDataSource(data) - button = Button() - - def modify_doc(doc): - plot = Plot(height=400, width=400, x_range=Range1d(0, 1), y_range=Range1d(0, 1), min_border=0) - plot.tags.append(CustomJS(name="custom-action", args=dict(s=source), code=RECORD("data", "s.data"))) - - button.js_on_event('button_click', CustomJS(args=dict(s=source), code="s.patch({'x': [[1, 100]]})")) - doc.add_root(column(button, plot)) - - page = bokeh_server_page(modify_doc) - - page.eval_custom_action() - results = page.results - - assert results == {'data': {'x': [1,2,3,4], 'y': [10,20,30,40]}} - assert source.data == {'x': [1,2,3,4], 'y': [10,20,30,40]} - - button_el = find_element_for(page.driver, button) - button_el.click() - - page.eval_custom_action() - results = page.results - - assert results == {'data': {'x': [1,100,3,4], 'y': [10,20,30,40]}} - assert source.data == {'x': [1,100,3,4], 'y': [10,20,30,40]} - - # confirm patch received but no full update - patch_events = 0 - for msg in page.message_test_port.received: - evts = msg.content.get('events', []) - assert not any(is_cds_data_changed(evt) for evt in evts) - patch_events += sum(is_cds_data_patched(evt) for evt in evts) - assert patch_events == 1 - - # confirm no ping-pong - for msg in page.message_test_port.sent: - evts = msg.content.get('events', []) - assert not any(is_cds_data_patched(evt) for evt in evts) - - assert page.has_no_console_errors() - - def test_client_source_stream_sends_patch_event(self, bokeh_server_page: BokehServerPage) -> None: - data = {'x': [1,2,3,4], 'y': [10,20,30,40]} - source = ColumnDataSource(data) - button = Button() - - def modify_doc(doc): - plot = Plot(height=400, width=400, x_range=Range1d(0, 1), y_range=Range1d(0, 1), min_border=0) - plot.tags.append(CustomJS(name="custom-action", args=dict(s=source), code=RECORD("data", "s.data"))) - - button.js_on_event('button_click', CustomJS(args=dict(s=source), code="s.stream({'x': [100], 'y': [200]})")) - doc.add_root(column(button, plot)) - - page = bokeh_server_page(modify_doc) - - page.eval_custom_action() - results = page.results - - assert results == {'data': {'x': [1,2,3,4], 'y': [10,20,30,40]}} - assert source.data == {'x': [1,2,3,4], 'y': [10,20,30,40]} - - button_el = find_element_for(page.driver, button) - button_el.click() - - page.eval_custom_action() - results = page.results - - assert results == {'data': {'x': [1,2,3,4,100], 'y': [10,20,30,40,200]}} - assert source.data == {'x': [1,2,3,4,100], 'y': [10,20,30,40,200]} - - # confirm stream received but no full update - stream_events = 0 - for msg in page.message_test_port.received: - evts = msg.content.get('events', []) - assert not any(is_cds_data_changed(evt) for evt in evts) - stream_events += sum(is_cds_data_streamed(evt) for evt in evts) - assert stream_events == 1 - - # confirm no ping-pong - for msg in page.message_test_port.sent: - evts = msg.content.get('events', []) - assert not any(is_cds_data_streamed(evt) for evt in evts) - - assert page.has_no_console_errors() diff --git a/tests/integration/test_regressions.py b/tests/integration/test_regressions.py deleted file mode 100644 index 06044db3909..00000000000 --- a/tests/integration/test_regressions.py +++ /dev/null @@ -1,38 +0,0 @@ -#----------------------------------------------------------------------------- -# Copyright (c) Anaconda, Inc. All rights reserved. -# -# Powered by the Bokeh Development Team. -# -# The full license is in the file LICENSE.txt, distributed with this software. -#----------------------------------------------------------------------------- - -#----------------------------------------------------------------------------- -# Boilerplate -#----------------------------------------------------------------------------- -from __future__ import annotations # isort:skip - -import pytest ; pytest - -#----------------------------------------------------------------------------- -# Imports -#----------------------------------------------------------------------------- - -# Bokeh imports -from bokeh.models import Plot -from tests.support.plugins.project import SinglePlotPage - -#----------------------------------------------------------------------------- -# Tests -#----------------------------------------------------------------------------- - -pytest_plugins = ( - "tests.support.plugins.project", -) - -@pytest.mark.selenium -class Test_regressions: - def test_issue_11694(self, single_plot_page: SinglePlotPage) -> None: - plot = Plot(height=400, width=400, tags=[dict(id="1000")]) - page = single_plot_page(plot) - - assert page.has_no_console_errors() diff --git a/tests/integration/tools/__init__.py b/tests/integration/tools/__init__.py deleted file mode 100644 index c8c348f1b36..00000000000 --- a/tests/integration/tools/__init__.py +++ /dev/null @@ -1,7 +0,0 @@ -#----------------------------------------------------------------------------- -# Copyright (c) Anaconda, Inc. All rights reserved. -# -# Powered by the Bokeh Development Team. -# -# The full license is in the file LICENSE.txt, distributed with this software. -#----------------------------------------------------------------------------- diff --git a/tests/integration/tools/test_box_edit_tool.py b/tests/integration/tools/test_box_edit_tool.py deleted file mode 100644 index 910a19f425e..00000000000 --- a/tests/integration/tools/test_box_edit_tool.py +++ /dev/null @@ -1,270 +0,0 @@ -#----------------------------------------------------------------------------- -# Copyright (c) Anaconda, Inc. All rights reserved. -# -# Powered by the Bokeh Development Team. -# -# The full license is in the file LICENSE.txt, distributed with this software. -#----------------------------------------------------------------------------- - -#----------------------------------------------------------------------------- -# Boilerplate -#----------------------------------------------------------------------------- -from __future__ import annotations # isort:skip - -import pytest ; pytest - -#----------------------------------------------------------------------------- -# Imports -#----------------------------------------------------------------------------- - -# Standard library imports -import time - -# Bokeh imports -from bokeh.application.handlers.function import ModifyDoc -from bokeh.layouts import column -from bokeh.models import ( - BoxEditTool, - ColumnDataSource, - CustomJS, - Div, - Plot, - Range1d, - Rect, -) -from tests.support.plugins.project import BokehServerPage, SinglePlotPage -from tests.support.util.compare import cds_data_almost_equal -from tests.support.util.selenium import RECORD - -#----------------------------------------------------------------------------- -# Tests -#----------------------------------------------------------------------------- - -pytest_plugins = ( - "tests.support.plugins.project", -) - -def _make_plot(dimensions="both", num_objects: int = 0) -> Plot: - source = ColumnDataSource(dict(x=[1, 2], y=[1, 1], width=[0.5, 0.5], height=[0.5, 0.5])) - plot = Plot(height=400, width=400, x_range=Range1d(0, 3), y_range=Range1d(0, 3), min_border=0) - renderer = plot.add_glyph(source, Rect(x='x', y='y', width='width', height='height')) - tool = BoxEditTool(dimensions=dimensions, num_objects=num_objects, renderers=[renderer]) - plot.add_tools(tool) - plot.toolbar.active_multi = tool - code = RECORD("x", "source.data.x", final=False) + \ - RECORD("y", "source.data.y", final=False) + \ - RECORD("width", "source.data.width", final=False) + \ - RECORD("height", "source.data.height") - plot.tags.append(CustomJS(name="custom-action", args=dict(source=source), code=code)) - plot.toolbar_sticky = False - return plot - -def _make_server_plot(expected, num_objects: int = 0) -> tuple[ModifyDoc, Plot]: - plot = Plot(height=400, width=400, x_range=Range1d(0, 3), y_range=Range1d(0, 3), min_border=0) - def modify_doc(doc): - source = ColumnDataSource(dict(x=[1, 2], y=[1, 1], width=[0.5, 0.5], height=[0.5, 0.5])) - renderer = plot.add_glyph(source, Rect(x='x', y='y', width='width', height='height')) - tool = BoxEditTool(dimensions='both', num_objects=num_objects, renderers=[renderer]) - plot.add_tools(tool) - plot.toolbar.active_multi = tool - div = Div(text='False') - def cb(attr, old, new): - if cds_data_almost_equal(new, expected): - div.text = 'True' - source.on_change('data', cb) - code = RECORD("matches", "div.text") - plot.tags.append(CustomJS(name="custom-action", args=dict(div=div), code=code)) - doc.add_root(column(plot, div)) - return modify_doc, plot - - -@pytest.mark.selenium -class Test_BoxEditTool: - def test_selected_by_default(self, single_plot_page: SinglePlotPage) -> None: - plot = _make_plot('both') - - page = single_plot_page(plot) - - [button] = page.get_toolbar_buttons(plot) - assert 'active' in button.get_attribute('class') - - assert page.has_no_console_errors() - - def test_can_be_deselected_and_selected(self, single_plot_page: SinglePlotPage) -> None: - plot = _make_plot('both') - - page = single_plot_page(plot) - - # Check is active - [button] = page.get_toolbar_buttons(plot) - assert 'active' in button.get_attribute('class') - - # Click and check is not active - [button] = page.get_toolbar_buttons(plot) - button.click() - assert 'active' not in button.get_attribute('class') - - # Click again and check is active - [button] = page.get_toolbar_buttons(plot) - button.click() - assert 'active' in button.get_attribute('class') - - assert page.has_no_console_errors() - - def test_double_click_triggers_draw(self, single_plot_page: SinglePlotPage) -> None: - plot = _make_plot('both') - - page = single_plot_page(plot) - - # ensure double clicking added a box - page.double_click_canvas_at_position(plot, 100, 100) - time.sleep(0.5) - page.double_click_canvas_at_position(plot, 200, 200) - time.sleep(0.5) - page.eval_custom_action() - - expected = {"x": [1, 2, 1.2162162162162162], - "y": [1, 1, 1.875], - "width": [0.5, 0.5, 0.8108108108108109], - "height": [0.5, 0.5, 0.75]} - assert cds_data_almost_equal(page.results, expected) - - assert page.has_no_console_errors() - - def test_shift_drag_triggers_draw(self, single_plot_page: SinglePlotPage) -> None: - plot = _make_plot('both') - - page = single_plot_page(plot) - - # ensure double clicking added a box - page.drag_canvas_at_position(plot, 100, 100, 50, 50, mod="\ue008") - time.sleep(0.5) - page.eval_custom_action() - expected = {"x": [1, 2, 1.0135135135135136], - "y": [1, 1, 2.0625], - "width": [0.5, 0.5, 0.4054054054054054], - "height": [0.5, 0.5, 0.3750000000000002]} - assert cds_data_almost_equal(page.results, expected) - - assert page.has_no_console_errors() - - def test_drag_moves_box(self, single_plot_page: SinglePlotPage) -> None: - plot = _make_plot('both') - - page = single_plot_page(plot) - - # ensure double clicking added a box - page.double_click_canvas_at_position(plot, 100, 100) - time.sleep(0.5) - page.double_click_canvas_at_position(plot, 200, 200) - time.sleep(0.5) - page.drag_canvas_at_position(plot, 150, 150, 50, 50) - time.sleep(0.5) - page.eval_custom_action() - - expected = {"x": [1, 2, 1.6216216216216217], - "y": [1, 1, 1.5000000000000002], - "width": [0.5, 0.5, 0.8108108108108109], - "height": [0.5, 0.5, 0.75]} - assert cds_data_almost_equal(page.results, expected) - - assert page.has_no_console_errors() - - def test_backspace_deletes_drawn_box(self, single_plot_page: SinglePlotPage) -> None: - plot = _make_plot('both', num_objects=2) - - page = single_plot_page(plot) - - # ensure backspace deletes box - page.double_click_canvas_at_position(plot, 100, 100) - time.sleep(0.5) - page.double_click_canvas_at_position(plot, 200, 200) - time.sleep(0.5) - page.click_canvas_at_position(plot, 150, 150) - time.sleep(0.5) - page.send_keys("\ue003") # Backspace - time.sleep(0.5) - - page.eval_custom_action() - - expected = {"x": [2], "y": [1], "width": [0.5], "height": [0.5]} - assert cds_data_almost_equal(page.results, expected) - - assert page.has_no_console_errors() - - def test_num_objects_limits_drawn_boxes(self, single_plot_page: SinglePlotPage) -> None: - plot = _make_plot('both', num_objects=2) - - page = single_plot_page(plot) - - # ensure double clicking added a box - page.drag_canvas_at_position(plot, 100, 100, 50, 50, mod="\ue008") - time.sleep(0.5) - page.eval_custom_action() - - expected = {"x": [2, 1.0135135135135136], - "y": [1, 2.0625], - "width": [0.5, 0.4054054054054054], - "height": [0.5, 0.3750000000000002]} - assert cds_data_almost_equal(page.results, expected) - - assert page.has_no_console_errors() - - def test_box_draw_syncs_to_server(self, bokeh_server_page: BokehServerPage) -> None: - expected = {"x": [1, 2, 1.2162162162162162], - "y": [1, 1, 1.875], - "width": [0.5, 0.5, 0.8108108108108109], - "height": [0.5, 0.5, 0.75]} - - modify_doc, plot = _make_server_plot(expected) - page = bokeh_server_page(modify_doc) - - # ensure double clicking added a box - page.double_click_canvas_at_position(plot, 100, 100) - time.sleep(0.5) - page.double_click_canvas_at_position(plot, 200, 200) - time.sleep(0.5) - - page.eval_custom_action() - assert page.results == {"matches": "True"} - - def test_box_drag_syncs_to_server(self, bokeh_server_page: BokehServerPage) -> None: - expected = {"x": [1, 2, 1.6216216216216217], - "y": [1, 1, 1.5000000000000002], - "width": [0.5, 0.5, 0.8108108108108109], - "height": [0.5, 0.5, 0.75]} - - modify_doc, plot = _make_server_plot(expected) - page = bokeh_server_page(modify_doc) - - # ensure drag moves box - page.double_click_canvas_at_position(plot, 100, 100) - time.sleep(0.5) - page.double_click_canvas_at_position(plot, 200, 200) - time.sleep(0.5) - page.drag_canvas_at_position(plot, 150, 150, 50, 50) - time.sleep(0.5) - page.eval_custom_action() - - page.eval_custom_action() - assert page.results == {"matches": "True"} - - def test_box_delete_syncs_to_server(self, bokeh_server_page: BokehServerPage) -> None: - expected = {"x": [2], "y": [1], - "width": [0.5], "height": [0.5]} - - modify_doc, plot = _make_server_plot(expected, num_objects=2) - page = bokeh_server_page(modify_doc) - - # ensure backspace deletes box - page.double_click_canvas_at_position(plot, 100, 100) - time.sleep(0.5) - page.double_click_canvas_at_position(plot, 200, 200) - time.sleep(0.5) - page.click_canvas_at_position(plot, 150, 150) - time.sleep(0.5) - page.send_keys("\ue003") # Backspace - time.sleep(0.5) - - page.eval_custom_action() - assert page.results == {"matches": "True"} diff --git a/tests/integration/tools/test_box_zoom_tool.py b/tests/integration/tools/test_box_zoom_tool.py deleted file mode 100644 index dc81aedb3b8..00000000000 --- a/tests/integration/tools/test_box_zoom_tool.py +++ /dev/null @@ -1,209 +0,0 @@ -#----------------------------------------------------------------------------- -# Copyright (c) Anaconda, Inc. All rights reserved. -# -# Powered by the Bokeh Development Team. -# -# The full license is in the file LICENSE.txt, distributed with this software. -#----------------------------------------------------------------------------- - -#----------------------------------------------------------------------------- -# Boilerplate -#----------------------------------------------------------------------------- -from __future__ import annotations # isort:skip - -import pytest ; pytest - -#----------------------------------------------------------------------------- -# Imports -#----------------------------------------------------------------------------- - -# Bokeh imports -from bokeh.core.enums import DimensionsType -from bokeh.models import ( - BoxZoomTool, - ColumnDataSource, - CustomJS, - Plot, - Range1d, - Rect, -) -from tests.support.plugins.project import SinglePlotPage -from tests.support.util.selenium import RECORD - -#----------------------------------------------------------------------------- -# Tests -#----------------------------------------------------------------------------- - -pytest_plugins = ( - "tests.support.plugins.project", -) - -def _make_plot(tool): - source = ColumnDataSource(dict(x=[1, 2], y=[1, 1])) - plot = Plot(height=400, width=450, min_border_right=50, x_range=Range1d(0, 1), y_range=Range1d(0, 1), min_border=0) - plot.add_glyph(source, Rect(x='x', y='y', width=0.9, height=0.9)) - plot.add_tools(tool) - code = RECORD("xrstart", "p.x_range.start", final=False) + \ - RECORD("xrend", "p.x_range.end", final=False) + \ - RECORD("yrstart", "p.y_range.start", final=False) + \ - RECORD("yrend", "p.y_range.end") - plot.tags.append(CustomJS(name="custom-action", args=dict(p=plot), code=code)) - plot.toolbar_sticky = False - return plot - - -@pytest.mark.selenium -class Test_BoxZoomTool: - - @pytest.mark.parametrize('dim', ['both', 'width', 'height']) - def test_box_zoom_has_no_effect_when_deslected(self, dim: DimensionsType, single_plot_page: SinglePlotPage) -> None: - plot = _make_plot(BoxZoomTool(dimensions=dim)) - - page = single_plot_page(plot) - - [button] = page.get_toolbar_buttons(plot) - button.click() - - page.drag_canvas_at_position(plot, 100, 100, 20, 20) - - page.eval_custom_action() - - results = page.results - assert results['xrstart'] == 0 - assert results['xrend'] == 1 - assert results['yrstart'] == 0 - assert results['yrend'] == 1 - - assert page.has_no_console_errors() - - def test_box_zoom_with_corner_origin(self, single_plot_page: SinglePlotPage) -> None: - plot = _make_plot(BoxZoomTool()) - - page = single_plot_page(plot) - - page.drag_canvas_at_position(plot, 100, 100, 200, 200) - - page.eval_custom_action() - - results = page.results - assert results['xrstart'] == pytest.approx(0.25) - assert results['xrend'] == pytest.approx(0.75) - assert results['yrstart'] == pytest.approx(0.25) - assert results['yrend'] == pytest.approx(0.75) - - assert page.has_no_console_errors() - - def test_box_zoom_with_center_origin(self, single_plot_page: SinglePlotPage) -> None: - plot = _make_plot(BoxZoomTool(origin="center")) - - page = single_plot_page(plot) - - page.drag_canvas_at_position(plot, 100, 100, 50, 50) - - page.eval_custom_action() - - results = page.results - assert (results['xrstart'] + results['xrend'])/2.0 == pytest.approx(0.25) - assert (results['yrstart'] + results['yrend'])/2.0 == pytest.approx(0.75) - - assert page.has_no_console_errors() - - def test_box_zoom_with_center_origin_clips_to_range(self, single_plot_page: SinglePlotPage) -> None: - plot = _make_plot(BoxZoomTool(origin="center")) - - page = single_plot_page(plot) - - page.drag_canvas_at_position(plot, 200, 200, 500, 500) - - page.eval_custom_action() - - results = page.results - assert results['xrstart'] == 0 - assert results['xrend'] == 1 - assert results['yrstart'] == 0 - assert results['yrend'] == 1 - - assert page.has_no_console_errors() - - def test_box_zoom_width_updates_only_xrange(self, single_plot_page: SinglePlotPage) -> None: - plot = _make_plot(BoxZoomTool(dimensions="width")) - - page = single_plot_page(plot) - - page.drag_canvas_at_position(plot, 250, 250, 50, 50) - - page.eval_custom_action() - - results = page.results - assert results['xrstart'] > 0.5 - assert results['xrend'] < 1 - assert results['yrstart'] == 0 - assert results['yrend'] == 1 - - assert page.has_no_console_errors() - - def test_box_zoom_width_clips_to_xrange(self, single_plot_page: SinglePlotPage) -> None: - plot = _make_plot(BoxZoomTool(dimensions="width")) - - page = single_plot_page(plot) - - page.drag_canvas_at_position(plot, 250, 250, 500, 50) - - page.eval_custom_action() - - results = page.results - assert results['xrstart'] > 0.5 - assert results['xrend'] == 1 - assert results['yrstart'] == 0 - assert results['yrend'] == 1 - - assert page.has_no_console_errors() - - def test_box_zoom_height_updates_only_yrange(self, single_plot_page: SinglePlotPage) -> None: - plot = _make_plot(BoxZoomTool(dimensions="height")) - - page = single_plot_page(plot) - - page.drag_canvas_at_position(plot, 250, 250, 50, 50) - - page.eval_custom_action() - - results = page.results - assert results['xrstart'] == 0 - assert results['xrend'] == 1 - assert results['yrstart'] > 0 - assert results['yrend'] < 0.5 - - assert page.has_no_console_errors() - - def test_box_zoom_height_clips_to_yrange(self, single_plot_page: SinglePlotPage) -> None: - plot = _make_plot(BoxZoomTool(dimensions="height")) - - page = single_plot_page(plot) - - page.drag_canvas_at_position(plot, 250, 250, 50, 500) - - page.eval_custom_action() - - results = page.results - assert results['xrstart'] == 0 - assert results['xrend'] == 1 - assert results['yrstart'] == 0 - assert results['yrend'] < 0.5 - - assert page.has_no_console_errors() - - def test_box_zoom_can_match_aspect(self, single_plot_page: SinglePlotPage) -> None: - plot = _make_plot(BoxZoomTool(match_aspect=True)) - plot.x_range.end = 2 - - page = single_plot_page(plot) - - page.drag_canvas_at_position(plot, 150, 150, 70, 53) - - page.eval_custom_action() - - results = page.results - assert (results['xrend'] - results['xrstart']) / (results['yrend'] - results['yrstart']) == pytest.approx(2.0) - - assert page.has_no_console_errors() diff --git a/tests/integration/tools/test_custom_action.py b/tests/integration/tools/test_custom_action.py deleted file mode 100644 index bb22cd98b2b..00000000000 --- a/tests/integration/tools/test_custom_action.py +++ /dev/null @@ -1,48 +0,0 @@ -#----------------------------------------------------------------------------- -# Copyright (c) Anaconda, Inc. All rights reserved. -# -# Powered by the Bokeh Development Team. -# -# The full license is in the file LICENSE.txt, distributed with this software. -#----------------------------------------------------------------------------- - -#----------------------------------------------------------------------------- -# Boilerplate -#----------------------------------------------------------------------------- -from __future__ import annotations # isort:skip - -import pytest ; pytest - -#----------------------------------------------------------------------------- -# Imports -#----------------------------------------------------------------------------- - -# Bokeh imports -from bokeh.models import CustomAction, CustomJS -from bokeh.plotting import figure -from tests.support.plugins.project import SinglePlotPage -from tests.support.util.selenium import RECORD - -#----------------------------------------------------------------------------- -# Tests -#----------------------------------------------------------------------------- - -pytest_plugins = ( - "tests.support.plugins.project", -) - - -@pytest.mark.selenium -class Test_CustomAction: - def test_tap_triggers_callback(self, single_plot_page: SinglePlotPage) -> None: - plot = figure(height=800, width=1000, tools='') - plot.rect(x=[1, 2], y=[1, 1], width=1, height=1) - plot.add_tools(CustomAction(icon=".bk-tool-icon-custom-action", callback=CustomJS(code=RECORD("activated", "true")))) - - page = single_plot_page(plot) - - [button] = page.get_toolbar_buttons(plot) - button.click() - assert page.results["activated"] is True - - assert page.has_no_console_errors() diff --git a/tests/integration/tools/test_freehand_draw_tool.py b/tests/integration/tools/test_freehand_draw_tool.py deleted file mode 100644 index b7ddc1cdf3c..00000000000 --- a/tests/integration/tools/test_freehand_draw_tool.py +++ /dev/null @@ -1,167 +0,0 @@ -#----------------------------------------------------------------------------- -# Copyright (c) Anaconda, Inc. All rights reserved. -# -# Powered by the Bokeh Development Team. -# -# The full license is in the file LICENSE.txt, distributed with this software. -#----------------------------------------------------------------------------- - -#----------------------------------------------------------------------------- -# Boilerplate -#----------------------------------------------------------------------------- -from __future__ import annotations # isort:skip - -import pytest ; pytest - -#----------------------------------------------------------------------------- -# Imports -#----------------------------------------------------------------------------- - -# Standard library imports -import time - -# Bokeh imports -from bokeh.application.handlers.function import ModifyDoc -from bokeh.layouts import column -from bokeh.models import ( - ColumnDataSource, - CustomJS, - Div, - FreehandDrawTool, - MultiLine, - Plot, - Range1d, -) -from tests.support.plugins.project import BokehServerPage, SinglePlotPage -from tests.support.util.compare import cds_data_almost_equal -from tests.support.util.selenium import RECORD - -#----------------------------------------------------------------------------- -# Tests -#----------------------------------------------------------------------------- - -pytest_plugins = ( - "tests.support.plugins.project", -) - -def _make_plot(num_objects=0): - source = ColumnDataSource(dict(xs=[], ys=[])) - plot = Plot(height=400, width=400, x_range=Range1d(0, 3), y_range=Range1d(0, 3), min_border=0) - renderer = plot.add_glyph(source, MultiLine(xs='xs', ys='ys')) - tool = FreehandDrawTool(num_objects=num_objects, renderers=[renderer]) - plot.add_tools(tool) - plot.toolbar.active_multi = tool - code = RECORD("xs", "source.data.xs", final=False) + RECORD("ys", "source.data.ys") - plot.tags.append(CustomJS(name="custom-action", args=dict(source=source), code=code)) - plot.toolbar_sticky = False - return plot - -def _make_server_plot(expected, num_objects=0) -> tuple[ModifyDoc, Plot]: - plot = Plot(height=400, width=400, x_range=Range1d(0, 3), y_range=Range1d(0, 3), min_border=0) - def modify_doc(doc): - source = ColumnDataSource(dict(xs=[], ys=[])) - renderer = plot.add_glyph(source, MultiLine(xs='xs', ys='ys')) - tool = FreehandDrawTool(num_objects=num_objects, renderers=[renderer]) - plot.add_tools(tool) - plot.toolbar.active_multi = tool - div = Div(text='False') - def cb(attr, old, new): - if cds_data_almost_equal(new, expected): - div.text = 'True' - source.on_change('data', cb) - code = RECORD("matches", "div.text") - plot.tags.append(CustomJS(name="custom-action", args=dict(div=div), code=code)) - doc.add_root(column(plot, div)) - return modify_doc, plot - - -@pytest.mark.selenium -class Test_FreehandDrawTool: - def test_selected_by_default(self, single_plot_page: SinglePlotPage) -> None: - plot = _make_plot() - - page = single_plot_page(plot) - - [button] = page.get_toolbar_buttons(plot) - assert 'active' in button.get_attribute('class') - - assert page.has_no_console_errors() - - def test_can_be_deselected_and_selected(self, single_plot_page: SinglePlotPage) -> None: - plot = _make_plot() - - page = single_plot_page(plot) - - # Check is active - [button] = page.get_toolbar_buttons(plot) - assert 'active' in button.get_attribute('class') - - # Click and check is not active - [button] = page.get_toolbar_buttons(plot) - button.click() - assert 'active' not in button.get_attribute('class') - - # Click again and check is active - [button] = page.get_toolbar_buttons(plot) - button.click() - assert 'active' in button.get_attribute('class') - - assert page.has_no_console_errors() - - def test_drag_triggers_draw(self, single_plot_page: SinglePlotPage) -> None: - plot = _make_plot() - - page = single_plot_page(plot) - - # ensure clicking adds a point - page.drag_canvas_at_position(plot, 200, 200, 50, 50) - page.eval_custom_action() - - expected = {'xs': [[1.6216216216216217, 2.027027027027027, 2.027027027027027, 2.027027027027027]], - 'ys': [[1.5, 1.125, 1.125, 1.125]]} - assert cds_data_almost_equal(page.results, expected) - - assert page.has_no_console_errors() - - def test_num_object_limits_lines(self, single_plot_page: SinglePlotPage) -> None: - plot = _make_plot(num_objects=1) - - page = single_plot_page(plot) - - # ensure clicking adds a point - page.drag_canvas_at_position(plot, 200, 200, 50, 50) - page.drag_canvas_at_position(plot, 100, 100, 100, 100) - page.eval_custom_action() - - expected = {'xs': [[0.8108108108108109, 1.6216216216216217, 1.6216216216216217, 1.6216216216216217]], - 'ys': [[2.25, 1.5, 1.5, 1.5]]} - assert cds_data_almost_equal(page.results, expected) - - assert page.has_no_console_errors() - - def test_freehand_draw_syncs_to_server(self, bokeh_server_page: BokehServerPage) -> None: - expected = {'xs': [[1.6216216216216217, 2.027027027027027, 2.027027027027027, 2.027027027027027]], - 'ys': [[1.5, 1.125, 1.125, 1.125]]} - - modify_doc, plot = _make_server_plot(expected) - page = bokeh_server_page(modify_doc) - - page.drag_canvas_at_position(plot, 200, 200, 50, 50) - page.eval_custom_action() - - assert page.results == {"matches": "True"} - - def test_line_delete_syncs_to_server(self, bokeh_server_page: BokehServerPage) -> None: - expected = {'xs': [], 'ys': []} - - modify_doc, plot = _make_server_plot(expected) - page = bokeh_server_page(modify_doc) - - # ensure clicking adds a point - page.drag_canvas_at_position(plot, 200, 200, 50, 50) - page.click_canvas_at_position(plot, 200, 200) - time.sleep(0.4) # hammerJS click timeout - page.send_keys("\ue003") # Backspace - - page.eval_custom_action() - assert page.results == {"matches": "True"} diff --git a/tests/integration/tools/test_pan_tool.py b/tests/integration/tools/test_pan_tool.py deleted file mode 100644 index a5fe8e3cbfa..00000000000 --- a/tests/integration/tools/test_pan_tool.py +++ /dev/null @@ -1,188 +0,0 @@ -#----------------------------------------------------------------------------- -# Copyright (c) Anaconda, Inc. All rights reserved. -# -# Powered by the Bokeh Development Team. -# -# The full license is in the file LICENSE.txt, distributed with this software. -#----------------------------------------------------------------------------- - -#----------------------------------------------------------------------------- -# Boilerplate -#----------------------------------------------------------------------------- -from __future__ import annotations # isort:skip - -import pytest ; pytest - -#----------------------------------------------------------------------------- -# Imports -#----------------------------------------------------------------------------- - -# Bokeh imports -from bokeh.core.enums import DimensionsType -from bokeh.events import RangesUpdate -from bokeh.models import ( - ColumnDataSource, - CustomJS, - PanTool, - Plot, - Range1d, - Rect, -) -from tests.support.plugins.project import SinglePlotPage -from tests.support.util.selenium import RECORD - -#----------------------------------------------------------------------------- -# Tests -#----------------------------------------------------------------------------- - -pytest_plugins = ( - "tests.support.plugins.project", -) - -def _make_plot(dimensions: DimensionsType = "both"): - source = ColumnDataSource(dict(x=[1, 2], y=[1, 1])) - plot = Plot(height=400, width=400, x_range=Range1d(0, 1), y_range=Range1d(0, 1), min_border=0) - plot.add_glyph(source, Rect(x='x', y='y', width=0.9, height=0.9)) - plot.add_tools(PanTool(dimensions=dimensions)) - code = RECORD("xrstart", "p.x_range.start", final=False) + \ - RECORD("xrend", "p.x_range.end", final=False) + \ - RECORD("yrstart", "p.y_range.start", final=False) + \ - RECORD("yrend", "p.y_range.end") - plot.tags.append(CustomJS(name="custom-action", args=dict(p=plot), code=code)) - plot.toolbar_sticky = False - return plot - -@pytest.mark.selenium -class Test_PanTool: - @pytest.mark.parametrize('dim', ['both', 'width', 'height']) - def test_selected_by_default(self, dim: DimensionsType, single_plot_page: SinglePlotPage) -> None: - plot = _make_plot(dim) - page = single_plot_page(plot) - - [button] = page.get_toolbar_buttons(plot) - assert 'active' in button.get_attribute('class') - - assert page.has_no_console_errors() - - @pytest.mark.parametrize('dim', ['both', 'width', 'height']) - def test_can_be_deselected_and_selected(self, dim: DimensionsType, single_plot_page: SinglePlotPage) -> None: - plot = _make_plot(dim) - - page = single_plot_page(plot) - - # Check is active - [button] = page.get_toolbar_buttons(plot) - assert 'active' in button.get_attribute('class') - - # Click and check is not active - [button] = page.get_toolbar_buttons(plot) - button.click() - assert 'active' not in button.get_attribute('class') - - # Click again and check is active - [button] = page.get_toolbar_buttons(plot) - button.click() - assert 'active' in button.get_attribute('class') - - assert page.has_no_console_errors() - - @pytest.mark.parametrize('dim', ['both', 'width', 'height']) - def test_pan_has_no_effect_when_deslected(self, dim: DimensionsType, single_plot_page: SinglePlotPage) -> None: - plot = _make_plot(dim) - - page = single_plot_page(plot) - - [button] = page.get_toolbar_buttons(plot) - button.click() - - page.drag_canvas_at_position(plot, 100, 100, 20, 20) - - page.eval_custom_action() - - results = page.results - assert results['xrstart'] == 0 - assert results['xrend'] == 1 - assert results['yrstart'] == 0 - assert results['yrend'] == 1 - - assert page.has_no_console_errors() - - def test_pan_updates_both_ranges(self, single_plot_page: SinglePlotPage) -> None: - plot = _make_plot() - - page = single_plot_page(plot) - - page.drag_canvas_at_position(plot, 100, 100, 20, 20) - - page.eval_custom_action() - - results = page.results - assert results['xrstart'] < 0 - assert results['xrend'] < 1 - assert results['yrstart'] > 0 - assert results['yrend'] > 1 - - assert page.has_no_console_errors() - - def test_xpan_upates_only_xrange(self, single_plot_page: SinglePlotPage) -> None: - plot = _make_plot('width') - - page = single_plot_page(plot) - - page.drag_canvas_at_position(plot, 100, 100, 20, 20) - - page.eval_custom_action() - - results = page.results - assert results['xrstart'] < 0 - assert results['xrend'] < 1 - assert results['yrstart'] == 0 - assert results['yrend'] == 1 - - assert page.has_no_console_errors() - - def test_ypan_updates_only_yrange(self, single_plot_page: SinglePlotPage) -> None: - plot = _make_plot('height') - - page = single_plot_page(plot) - - page.drag_canvas_at_position(plot, 100, 100, 20, 20) - - page.eval_custom_action() - - results = page.results - assert results['xrstart'] == 0 - assert results['xrend'] == 1 - assert results['yrstart'] > 0 - assert results['yrend'] > 1 - - assert page.has_no_console_errors() - - def test_ranges_update(self, single_plot_page: SinglePlotPage) -> None: - source = ColumnDataSource(dict(x=[1, 2], y=[1, 1])) - plot = Plot(height=400, width=400, x_range=Range1d(0, 1), y_range=Range1d(0, 1), min_border=0) - plot.add_glyph(source, Rect(x='x', y='y', width=0.9, height=0.9)) - plot.add_tools(PanTool()) - code = RECORD("event_name", "cb_obj.event_name", final=False) + \ - RECORD("x0", "cb_obj.x0", final=False) + \ - RECORD("x1", "cb_obj.x1", final=False) + \ - RECORD("y0", "cb_obj.y0", final=False) + \ - RECORD("y1", "cb_obj.y1") - plot.js_on_event(RangesUpdate, CustomJS(code=code)) - plot.tags.append(CustomJS(name="custom-action", code="")) - plot.toolbar_sticky = False - - page = single_plot_page(plot) - - page.drag_canvas_at_position(plot, 100, 100, 20, 20) - - page.eval_custom_action() - - results = page.results - assert results['event_name'] == "rangesupdate" - assert results['x0'] < 0 - assert results['x1'] < 1 - assert results['y0'] > 0 - assert results['y1'] > 1 - - assert page.has_no_console_errors() diff --git a/tests/integration/tools/test_point_draw_tool.py b/tests/integration/tools/test_point_draw_tool.py deleted file mode 100644 index 8c02f2adebe..00000000000 --- a/tests/integration/tools/test_point_draw_tool.py +++ /dev/null @@ -1,234 +0,0 @@ -#----------------------------------------------------------------------------- -# Copyright (c) Anaconda, Inc. All rights reserved. -# -# Powered by the Bokeh Development Team. -# -# The full license is in the file LICENSE.txt, distributed with this software. -#----------------------------------------------------------------------------- - -#----------------------------------------------------------------------------- -# Boilerplate -#----------------------------------------------------------------------------- -from __future__ import annotations # isort:skip - -import pytest ; pytest - -#----------------------------------------------------------------------------- -# Imports -#----------------------------------------------------------------------------- - -# Standard library imports -import time - -# Bokeh imports -from bokeh.application.handlers.function import ModifyDoc -from bokeh.layouts import column -from bokeh.models import ( - Circle, - ColumnDataSource, - CustomJS, - Div, - Plot, - PointDrawTool, - Range1d, -) -from tests.support.plugins.project import BokehServerPage, SinglePlotPage -from tests.support.util.compare import cds_data_almost_equal -from tests.support.util.selenium import RECORD - -#----------------------------------------------------------------------------- -# Tests -#----------------------------------------------------------------------------- - -pytest_plugins = ( - "tests.support.plugins.project", -) - -def _make_plot(num_objects=0, add=True, drag=True): - source = ColumnDataSource(dict(x=[1, 2], y=[1, 1])) - plot = Plot(height=400, width=400, x_range=Range1d(0, 3), y_range=Range1d(0, 3), min_border=0) - renderer = plot.add_glyph(source, Circle(x='x', y='y')) - tool = PointDrawTool(num_objects=num_objects, add=add, drag=drag, renderers=[renderer]) - plot.add_tools(tool) - plot.toolbar.active_multi = tool - code = RECORD("x", "source.data.x", final=False) + RECORD("y", "source.data.y") - plot.tags.append(CustomJS(name="custom-action", args=dict(source=source), code=code)) - plot.toolbar_sticky = False - return plot - -def _make_server_plot(expected) -> tuple[ModifyDoc, Plot]: - plot = Plot(height=400, width=400, x_range=Range1d(0, 3), y_range=Range1d(0, 3), min_border=0) - def modify_doc(doc): - source = ColumnDataSource(dict(x=[1, 2], y=[1, 1])) - renderer = plot.add_glyph(source, Circle(x='x', y='y')) - tool = PointDrawTool(renderers=[renderer]) - plot.add_tools(tool) - plot.toolbar.active_multi = tool - div = Div(text='False') - def cb(attr, old, new): - if cds_data_almost_equal(new, expected): - div.text = 'True' - source.on_change('data', cb) - code = RECORD("matches", "div.text") - plot.tags.append(CustomJS(name="custom-action", args=dict(div=div), code=code)) - doc.add_root(column(plot, div)) - return modify_doc, plot - - -@pytest.mark.selenium -class Test_PointDrawTool: - def test_selected_by_default(self, single_plot_page: SinglePlotPage) -> None: - plot = _make_plot() - - page = single_plot_page(plot) - - [button] = page.get_toolbar_buttons(plot) - assert 'active' in button.get_attribute('class') - - assert page.has_no_console_errors() - - def test_can_be_deselected_and_selected(self, single_plot_page: SinglePlotPage) -> None: - plot = _make_plot() - - page = single_plot_page(plot) - - # Check is active - [button] = page.get_toolbar_buttons(plot) - assert 'active' in button.get_attribute('class') - - # Click and check is not active - [button] = page.get_toolbar_buttons(plot) - button.click() - assert 'active' not in button.get_attribute('class') - - # Click again and check is active - [button] = page.get_toolbar_buttons(plot) - button.click() - assert 'active' in button.get_attribute('class') - - assert page.has_no_console_errors() - - def test_click_triggers_draw(self, single_plot_page: SinglePlotPage) -> None: - plot = _make_plot() - - page = single_plot_page(plot) - - # ensure clicking adds a point - page.click_canvas_at_position(plot, 200, 200) - time.sleep(0.4) # hammerJS click timeout - page.eval_custom_action() - - expected = {"x": [1, 2, 1.6216216216216217], - "y": [1, 1, 1.5]} - assert cds_data_almost_equal(page.results, expected) - - assert page.has_no_console_errors() - - def test_click_does_not_trigger_draw(self, single_plot_page: SinglePlotPage) -> None: - plot = _make_plot(add=False) - - page = single_plot_page(plot) - - # ensure clicking does not add a point - page.click_canvas_at_position(plot, 200, 200) - time.sleep(0.4) # hammerJS click timeout - page.eval_custom_action() - - expected = {"x": [1, 2], "y": [1, 1]} - assert cds_data_almost_equal(page.results, expected) - - assert page.has_no_console_errors() - - def test_drag_moves_point(self, single_plot_page: SinglePlotPage) -> None: - plot = _make_plot() - - page = single_plot_page(plot) - - # ensure clicking adds a point - page.click_canvas_at_position(plot, 200, 200) - time.sleep(0.4) # hammerJS click timeout - page.drag_canvas_at_position(plot, 200, 200, 70, 53) - page.eval_custom_action() - - expected = {"x": [1, 2, 2.1891891891891895], - "y": [1, 1, 1.1024999999999998]} - assert cds_data_almost_equal(page.results, expected) - - assert page.has_no_console_errors() - - def test_drag_does_not_move_point(self, single_plot_page: SinglePlotPage) -> None: - plot = _make_plot(drag=False) - - page = single_plot_page(plot) - - # ensure clicking adds a point - page.click_canvas_at_position(plot, 200, 200) - time.sleep(0.4) # hammerJS click timeout - page.drag_canvas_at_position(plot, 200, 200, 70, 53) - page.eval_custom_action() - - expected = {"x": [1, 2, 1.6216216216216217], - "y": [1, 1, 1.5]} - assert cds_data_almost_equal(page.results, expected) - - assert page.has_no_console_errors() - - def test_num_object_limits_points(self, single_plot_page: SinglePlotPage) -> None: - plot = _make_plot(num_objects=2) - - page = single_plot_page(plot) - - # ensure clicking adds a point - page.click_canvas_at_position(plot, 200, 200) - time.sleep(0.4) # hammerJS click timeout - page.eval_custom_action() - - expected = {"x": [2, 1.6216216216216217], - "y": [1, 1.5]} - assert cds_data_almost_equal(page.results, expected) - - assert page.has_no_console_errors() - - def test_point_draw_syncs_to_server(self, bokeh_server_page: BokehServerPage) -> None: - expected = {"x": [1, 2, 1.6216216216216217], - "y": [1, 1, 1.5]} - - modify_doc, plot = _make_server_plot(expected) - page = bokeh_server_page(modify_doc) - - page.click_canvas_at_position(plot, 200, 200) - time.sleep(0.5) # hammerJS click timeout - - page.eval_custom_action() - assert page.results == {"matches": "True"} - - def test_point_drag_syncs_to_server(self, bokeh_server_page: BokehServerPage) -> None: - expected = {"x": [1, 2, 2.1891891891891895], - "y": [1, 1, 1.1024999999999998]} - - modify_doc, plot = _make_server_plot(expected) - page = bokeh_server_page(modify_doc) - - page.click_canvas_at_position(plot, 200, 200) - time.sleep(0.4) # hammerJS click timeout - page.drag_canvas_at_position(plot, 200, 200, 70, 53) - - page.eval_custom_action() - assert page.results == {"matches": "True"} - - def test_point_delete_syncs_to_server(self, bokeh_server_page: BokehServerPage) -> None: - expected = {"x": [1, 2], - "y": [1, 1]} - - modify_doc, plot = _make_server_plot(expected) - page = bokeh_server_page(modify_doc) - - page.click_canvas_at_position(plot, 200, 200) - time.sleep(0.4) # hammerJS click timeout - page.click_canvas_at_position(plot, 200, 200) - time.sleep(0.4) # hammerJS click timeout - page.send_keys("\ue003") # Backspace - time.sleep(0.4) # hammerJS click timeout - - page.eval_custom_action() - assert page.results == {"matches": "True"} diff --git a/tests/integration/tools/test_poly_draw_tool.py b/tests/integration/tools/test_poly_draw_tool.py deleted file mode 100644 index 5937c176a4c..00000000000 --- a/tests/integration/tools/test_poly_draw_tool.py +++ /dev/null @@ -1,253 +0,0 @@ -#----------------------------------------------------------------------------- -# Copyright (c) Anaconda, Inc. All rights reserved. -# -# Powered by the Bokeh Development Team. -# -# The full license is in the file LICENSE.txt, distributed with this software. -#----------------------------------------------------------------------------- - -#----------------------------------------------------------------------------- -# Boilerplate -#----------------------------------------------------------------------------- -from __future__ import annotations # isort:skip - -import pytest ; pytest - -#----------------------------------------------------------------------------- -# Imports -#----------------------------------------------------------------------------- - -# Standard library imports -import time - -# Bokeh imports -from bokeh.application.handlers.function import ModifyDoc -from bokeh.layouts import column -from bokeh.models import ( - ColumnDataSource, - CustomJS, - Div, - MultiLine, - Plot, - PolyDrawTool, - Range1d, - Scatter, -) -from tests.support.plugins.project import BokehServerPage, SinglePlotPage -from tests.support.util.compare import cds_data_almost_equal -from tests.support.util.selenium import RECORD - -#----------------------------------------------------------------------------- -# Tests -#----------------------------------------------------------------------------- - -pytest_plugins = ( - "tests.support.plugins.project", -) - -def _make_plot(num_objects=0, drag=True, vertices=False): - source = ColumnDataSource(dict(xs=[[1, 2]], ys=[[1, 1]])) - plot = Plot(height=400, width=400, x_range=Range1d(0, 3), y_range=Range1d(0, 3), min_border=0) - renderer = plot.add_glyph(source, MultiLine(xs='xs', ys='ys')) - tool = PolyDrawTool(num_objects=num_objects, drag=drag, renderers=[renderer]) - if vertices: - psource = ColumnDataSource(dict(x=[], y=[])) - prenderer = plot.add_glyph(psource, Scatter(x='x', y='y', size=10)) - tool.vertex_renderer = prenderer - plot.add_tools(tool) - plot.toolbar.active_multi = tool - code = RECORD("xs", "source.data.xs", final=False) + RECORD("ys", "source.data.ys") - plot.tags.append(CustomJS(name="custom-action", args=dict(source=source), code=code)) - plot.toolbar_sticky = False - return plot - -def _make_server_plot(expected) -> tuple[ModifyDoc, Plot]: - plot = Plot(height=400, width=400, x_range=Range1d(0, 3), y_range=Range1d(0, 3), min_border=0) - def modify_doc(doc): - source = ColumnDataSource(dict(xs=[[1, 2]], ys=[[1, 1]])) - renderer = plot.add_glyph(source, MultiLine(xs='xs', ys='ys')) - tool = PolyDrawTool(renderers=[renderer]) - plot.add_tools(tool) - plot.toolbar.active_multi = tool - div = Div(text='False') - def cb(attr, old, new): - if cds_data_almost_equal(new, expected): - div.text = 'True' - source.on_change('data', cb) - code = RECORD("matches", "div.text") - plot.tags.append(CustomJS(name="custom-action", args=dict(div=div), code=code)) - doc.add_root(column(plot, div)) - return modify_doc, plot - - -@pytest.mark.selenium -class Test_PolyDrawTool: - def test_selected_by_default(self, single_plot_page: SinglePlotPage) -> None: - plot = _make_plot() - - page = single_plot_page(plot) - - [button] = page.get_toolbar_buttons(plot) - assert 'active' in button.get_attribute('class') - - assert page.has_no_console_errors() - - def test_can_be_deselected_and_selected(self, single_plot_page: SinglePlotPage) -> None: - plot = _make_plot() - - page = single_plot_page(plot) - - # Check is active - [button] = page.get_toolbar_buttons(plot) - assert 'active' in button.get_attribute('class') - - # Click and check is not active - [button] = page.get_toolbar_buttons(plot) - button.click() - assert 'active' not in button.get_attribute('class') - - # Click again and check is active - [button] = page.get_toolbar_buttons(plot) - button.click() - assert 'active' in button.get_attribute('class') - - assert page.has_no_console_errors() - - def test_double_click_triggers_draw(self, single_plot_page: SinglePlotPage) -> None: - plot = _make_plot() - - page = single_plot_page(plot) - - # ensure double clicking adds a poly - page.double_click_canvas_at_position(plot, 200, 200) - page.double_click_canvas_at_position(plot, 300, 300) - time.sleep(0.5) - page.eval_custom_action() - - expected = {"xs": [[1, 2], [1.6216216216216217, 2.4324324324324325]], - "ys": [[1, 1], [1.5, 0.75]]} - assert cds_data_almost_equal(page.results, expected) - - assert page.has_no_console_errors() - - def test_click_snaps_to_vertex(self, single_plot_page: SinglePlotPage) -> None: - plot = _make_plot(vertices=True) - - page = single_plot_page(plot) - - # ensure double clicking adds a poly - page.double_click_canvas_at_position(plot, 200, 200) - page.click_canvas_at_position(plot, 300, 300) - time.sleep(0.5) - page.double_click_canvas_at_position(plot, 201, 201) - time.sleep(0.5) - page.eval_custom_action() - - expected = {"xs": [[1, 2], [1.6216216216216217, 2.4324324324324325, 1.6216216216216217]], - "ys": [[1, 1], [1.5, 0.75, 1.5]]} - assert cds_data_almost_equal(page.results, expected) - - assert page.has_no_console_errors() - - def test_drag_moves_multi_line(self, single_plot_page: SinglePlotPage) -> None: - plot = _make_plot() - - page = single_plot_page(plot) - - # ensure clicking adds a point - page.double_click_canvas_at_position(plot, 200, 200) - page.double_click_canvas_at_position(plot, 300, 300) - time.sleep(0.4) # hammerJS click timeout - page.drag_canvas_at_position(plot, 200, 200, 70, 50) - page.eval_custom_action() - - expected = {"xs": [[1, 2], [2.1891891891891895, 3]], - "ys": [[1, 1], [1.125, 0.375]]} - assert cds_data_almost_equal(page.results, expected) - - assert page.has_no_console_errors() - - def test_drag_does_not_move_multi_line(self, single_plot_page: SinglePlotPage) -> None: - plot = _make_plot(drag=False) - - page = single_plot_page(plot) - - # ensure clicking adds a point - page.double_click_canvas_at_position(plot, 200, 200) - page.double_click_canvas_at_position(plot, 300, 300) - time.sleep(0.4) # hammerJS click timeout - page.drag_canvas_at_position(plot, 200, 200, 70, 53) - page.eval_custom_action() - - expected = {"xs": [[1, 2], [1.6216216216216217, 2.4324324324324325]], - "ys": [[1, 1], [1.5, 0.75]] } - assert cds_data_almost_equal(page.results, expected) - - assert page.has_no_console_errors() - - def test_num_object_limits_multi_lines(self, single_plot_page: SinglePlotPage) -> None: - plot = _make_plot(num_objects=1) - - page = single_plot_page(plot) - - # ensure clicking adds a point - page.double_click_canvas_at_position(plot, 200, 200) - page.double_click_canvas_at_position(plot, 300, 300) - time.sleep(0.4) # hammerJS click timeout - page.drag_canvas_at_position(plot, 200, 200, 70, 50) - page.eval_custom_action() - - expected = {"xs": [[2.1891891891891895, 3]], - "ys": [[1.125, 0.375]]} - assert cds_data_almost_equal(page.results, expected) - - assert page.has_no_console_errors() - - def test_poly_draw_syncs_to_server(self, bokeh_server_page: BokehServerPage) -> None: - expected = {"xs": [[1, 2], [1.6216216216216217, 2.4324324324324325]], - "ys": [[1, 1], [1.5, 0.75]]} - - modify_doc, plot = _make_server_plot(expected) - page = bokeh_server_page(modify_doc) - - # ensure double clicking adds a poly - page.double_click_canvas_at_position(plot, 200, 200) - page.double_click_canvas_at_position(plot, 300, 300) - time.sleep(0.5) - - page.eval_custom_action() - assert page.results == {"matches": "True"} - - def test_poly_drag_syncs_to_server(self, bokeh_server_page: BokehServerPage) -> None: - expected = {"xs": [[1, 2], [2.1891891891891895, 3]], - "ys": [[1, 1], [1.125, 0.375]]} - - modify_doc, plot = _make_server_plot(expected) - page = bokeh_server_page(modify_doc) - - # ensure dragging move multi_line - page.double_click_canvas_at_position(plot, 200, 200) - page.double_click_canvas_at_position(plot, 300, 300) - time.sleep(0.4) # hammerJS click timeout - page.drag_canvas_at_position(plot, 200, 200, 70, 50) - - page.eval_custom_action() - assert page.results == {"matches": "True"} - - def test_poly_delete_syncs_to_server(self, bokeh_server_page: BokehServerPage) -> None: - expected = {"xs": [[1, 2]], - "ys": [[1, 1]]} - - modify_doc, plot = _make_server_plot(expected) - page = bokeh_server_page(modify_doc) - - page.double_click_canvas_at_position(plot, 200, 200) - page.double_click_canvas_at_position(plot, 300, 300) - time.sleep(0.4) # hammerJS click timeout - page.click_canvas_at_position(plot, 200, 200) - time.sleep(0.4) # hammerJS click timeout - page.send_keys("\ue003") # Backspace - time.sleep(0.4) # hammerJS click timeout - - page.eval_custom_action() - assert page.results == {"matches": "True"} diff --git a/tests/integration/tools/test_poly_edit_tool.py b/tests/integration/tools/test_poly_edit_tool.py deleted file mode 100644 index 00de54aea8f..00000000000 --- a/tests/integration/tools/test_poly_edit_tool.py +++ /dev/null @@ -1,303 +0,0 @@ -#----------------------------------------------------------------------------- -# Copyright (c) Anaconda, Inc. All rights reserved. -# -# Powered by the Bokeh Development Team. -# -# The full license is in the file LICENSE.txt, distributed with this software. -#----------------------------------------------------------------------------- - -#----------------------------------------------------------------------------- -# Boilerplate -#----------------------------------------------------------------------------- -from __future__ import annotations # isort:skip - -import pytest ; pytest - -#----------------------------------------------------------------------------- -# Imports -#----------------------------------------------------------------------------- - -# Standard library imports -import time - -# Bokeh imports -from bokeh.application.handlers.function import ModifyDoc -from bokeh.layouts import column -from bokeh.models import ( - ColumnDataSource, - CustomJS, - Div, - MultiLine, - Plot, - PolyEditTool, - Range1d, - Scatter, -) -from tests.support.plugins.project import BokehServerPage, SinglePlotPage -from tests.support.util.compare import cds_data_almost_equal -from tests.support.util.selenium import RECORD - -#----------------------------------------------------------------------------- -# Tests -#----------------------------------------------------------------------------- - -pytest_plugins = ( - "tests.support.plugins.project", -) - -def _make_plot() -> Plot: - data = {"xs": [[1, 2], [1.6, 2.45]], - "ys": [[1, 1], [1.5, 0.75]]} - source = ColumnDataSource(data) - plot = Plot(height=400, width=400, x_range=Range1d(0, 3), y_range=Range1d(0, 3), min_border=0) - renderer = plot.add_glyph(source, MultiLine(xs='xs', ys='ys', line_width=10)) - tool = PolyEditTool(renderers=[renderer]) - psource = ColumnDataSource(dict(x=[], y=[])) - prenderer = plot.add_glyph(psource, Scatter(x='x', y='y', size=10)) - tool.vertex_renderer = prenderer - plot.add_tools(tool) - plot.toolbar.active_multi = tool - code = RECORD("xs", "source.data.xs", final=False) + RECORD("ys", "source.data.ys") - plot.tags.append(CustomJS(name="custom-action", args=dict(source=source), code=code)) - plot.toolbar_sticky = False - return plot - -def _make_server_plot(expected) -> tuple[ModifyDoc, Plot, ColumnDataSource]: - data = {"xs": [[1, 2], [1.6, 2.45]], - "ys": [[1, 1], [1.5, 0.75]]} - source = ColumnDataSource(data) - plot = Plot(height=400, width=400, x_range=Range1d(0, 3), y_range=Range1d(0, 3), min_border=0) - def modify_doc(doc): - renderer = plot.add_glyph(source, MultiLine(xs='xs', ys='ys')) - tool = PolyEditTool(renderers=[renderer]) - psource = ColumnDataSource(dict(x=[], y=[])) - prenderer = plot.add_glyph(psource, Scatter(x='x', y='y', size=10)) - tool.vertex_renderer = prenderer - plot.add_tools(tool) - plot.toolbar.active_multi = tool - plot.toolbar_sticky = False - div = Div(text='False') - def cb(attr, old, new): - try: - if cds_data_almost_equal(new, expected): - div.text = 'True' - except ValueError: - return - source.on_change('data', cb) - code = RECORD("matches", "div.text") - plot.tags.append(CustomJS(name="custom-action", args=dict(div=div), code=code)) - doc.add_root(column(plot, div)) - return modify_doc, plot, source - - -@pytest.mark.selenium -class Test_PolyEditTool: - def test_selected_by_default(self, single_plot_page: SinglePlotPage): - plot = _make_plot() - - page = single_plot_page(plot) - - [button] = page.get_toolbar_buttons(plot) - assert 'active' in button.get_attribute('class') - - assert page.has_no_console_errors() - - def test_can_be_deselected_and_selected(self, single_plot_page: SinglePlotPage): - plot = _make_plot() - - page = single_plot_page(plot) - - # Check is active - [button] = page.get_toolbar_buttons(plot) - assert 'active' in button.get_attribute('class') - - # Click and check is not active - [button] = page.get_toolbar_buttons(plot) - button.click() - assert 'active' not in button.get_attribute('class') - - # Click again and check is active - [button] = page.get_toolbar_buttons(plot) - button.click() - assert 'active' in button.get_attribute('class') - - assert page.has_no_console_errors() - - def test_double_click_triggers_edit(self, single_plot_page: SinglePlotPage): - plot = _make_plot() - - page = single_plot_page(plot) - - # ensure double clicking shows vertices and edits them - page.double_click_canvas_at_position(plot, 200, 200) - time.sleep(0.5) - page.double_click_canvas_at_position(plot, 298, 298) - time.sleep(0.5) - page.double_click_canvas_at_position(plot, 250, 150) - time.sleep(0.5) - page.eval_custom_action() - - expected = {'xs': [[1, 2], [1.6, 2.45, 2.027027027027027]], - 'ys': [[1, 1], [1.5, 0.75, 1.8749999999999998]]} - assert cds_data_almost_equal(page.results, expected) - - assert page.has_no_console_errors() - - def test_double_click_snaps_to_vertex(self, single_plot_page: SinglePlotPage): - plot = _make_plot() - - page = single_plot_page(plot) - - # ensure double clicking snaps to vertex - page.double_click_canvas_at_position(plot, 200, 200) - time.sleep(0.5) - page.double_click_canvas_at_position(plot, 298, 298) - time.sleep(0.5) - page.click_canvas_at_position(plot, 250, 150) - time.sleep(0.5) - page.double_click_canvas_at_position(plot, 200, 200) - time.sleep(0.5) - page.eval_custom_action() - - expected = {"xs": [[1, 2], [1.6, 2.45, 2.027027027027027, 1.6]], - "ys": [[1, 1], [1.5, 0.75, 1.8749999999999998, 1.5]]} - assert cds_data_almost_equal(page.results, expected) - - assert page.has_no_console_errors() - - def test_drag_moves_vertex(self, single_plot_page: SinglePlotPage): - plot = _make_plot() - - page = single_plot_page(plot) - - # ensure drag moves vertex - page.double_click_canvas_at_position(plot, 200, 200) - time.sleep(0.5) - page.double_click_canvas_at_position(plot, 298, 298) - time.sleep(0.5) - page.click_canvas_at_position(plot, 250, 150) - time.sleep(0.5) - page.send_keys("\ue00c") # Escape - page.drag_canvas_at_position(plot, 250, 150, 70, 50) - time.sleep(0.5) - page.eval_custom_action() - - expected = {"xs": [[1, 2], [1.6, 2.45, 2.5945945945945947]], - "ys": [[1, 1], [1.5, 0.75, 1.5]]} - assert cds_data_almost_equal(page.results, expected) - - assert page.has_no_console_errors() - - def test_backspace_removes_vertex(self, single_plot_page: SinglePlotPage): - plot = _make_plot() - - page = single_plot_page(plot) - - # ensure drag moves vertex - page.double_click_canvas_at_position(plot, 200, 200) - time.sleep(0.5) - page.double_click_canvas_at_position(plot, 298, 298) - time.sleep(0.5) - page.click_canvas_at_position(plot, 250, 150) - time.sleep(0.5) - page.send_keys("\ue00c") # Escape - page.click_canvas_at_position(plot, 298, 298) - time.sleep(0.5) - page.send_keys("\ue003") # Escape - page.eval_custom_action() - - expected = {"xs": [[1, 2], [1.6, 2.027027027027027]], - "ys": [[1, 1], [1.5, 1.8749999999999998]]} - assert cds_data_almost_equal(page.results, expected) - assert page.has_no_console_errors() - - def test_poly_edit_syncs_to_server(self, bokeh_server_page: BokehServerPage): - expected = {'xs': [[1, 2], [1.6, 2.45, 2.027027027027027]], - 'ys': [[1, 1], [1.5, 0.75, 1.8749999999999998]]} - modify_doc, plot, _ = _make_server_plot(expected) - page = bokeh_server_page(modify_doc) - - # ensure double clicking shows vertices and edits them - page.double_click_canvas_at_position(plot, 200, 200) - time.sleep(0.5) - page.double_click_canvas_at_position(plot, 298, 298) - time.sleep(0.5) - page.double_click_canvas_at_position(plot, 250, 150) - time.sleep(0.5) - - page.eval_custom_action() - assert page.results == {"matches": "True"} - assert page.has_no_console_errors() - - def test_poly_drag_syncs_to_server(self, bokeh_server_page: BokehServerPage): - expected = {"xs": [[1, 2], [1.6, 2.45, 2.5945945945945947]], - "ys": [[1, 1], [1.5, 0.75, 1.5]]} - - modify_doc, plot, _ = _make_server_plot(expected) - page = bokeh_server_page(modify_doc) - - # ensure drag moves vertex - page.double_click_canvas_at_position(plot, 200, 200) - time.sleep(0.5) - page.double_click_canvas_at_position(plot, 298, 298) - time.sleep(0.5) - page.click_canvas_at_position(plot, 250, 150) - time.sleep(0.5) - page.send_keys("\ue00c") # Escape - page.drag_canvas_at_position(plot, 250, 150, 70, 50) - time.sleep(0.5) - - page.eval_custom_action() - assert page.results == {"matches": "True"} - assert page.has_no_console_errors() - - def test_poly_drag_sync_after_source_edit(self, bokeh_server_page: BokehServerPage): - expected = {"xs": [[1, 2], [1.6, 2.45, 2.5945945945945947]], - "ys": [[1, 1], [1.5, 0.75, 1.5]]} - - modify_doc, plot, ds = _make_server_plot(expected) - page = bokeh_server_page(modify_doc) - - # ensure drag moves vertex - page.double_click_canvas_at_position(plot, 200, 200) - time.sleep(0.5) - page.double_click_canvas_at_position(plot, 298, 298) - time.sleep(0.5) - page.click_canvas_at_position(plot, 250, 150) - time.sleep(0.5) - page.send_keys("\ue00c") # Escape - time.sleep(0.5) - - def f(): ds.data = dict(ds.data) # update the data source - ds.document.add_next_tick_callback(f) - time.sleep(0.5) - - page.drag_canvas_at_position(plot, 250, 150, 70, 50) - time.sleep(0.5) - - page.eval_custom_action() - assert page.results == {"matches": "True"} - assert page.has_no_console_errors() - - def test_poly_delete_syncs_to_server(self, bokeh_server_page: BokehServerPage) -> None: - expected = {"xs": [[1, 2], [1.6, 2.027027027027027]], - "ys": [[1, 1], [1.5, 1.8749999999999998]]} - - modify_doc, plot, _ = _make_server_plot(expected) - page = bokeh_server_page(modify_doc) - - # ensure backspace removes vertex - page.double_click_canvas_at_position(plot, 200, 200) - time.sleep(0.5) - page.double_click_canvas_at_position(plot, 298, 298) - time.sleep(0.5) - page.click_canvas_at_position(plot, 250, 150) - time.sleep(0.5) - page.send_keys("\ue00c") # Escape - page.click_canvas_at_position(plot, 298, 298) - time.sleep(0.5) - page.send_keys("\ue003") # Backspace - - page.eval_custom_action() - assert page.results == {"matches": "True"} - assert page.has_no_console_errors() diff --git a/tests/integration/tools/test_range_tool.py b/tests/integration/tools/test_range_tool.py deleted file mode 100644 index b351654aec4..00000000000 --- a/tests/integration/tools/test_range_tool.py +++ /dev/null @@ -1,296 +0,0 @@ -#----------------------------------------------------------------------------- -# Copyright (c) Anaconda, Inc. All rights reserved. -# -# Powered by the Bokeh Development Team. -# -# The full license is in the file LICENSE.txt, distributed with this software. -#----------------------------------------------------------------------------- - -#----------------------------------------------------------------------------- -# Boilerplate -#----------------------------------------------------------------------------- -from __future__ import annotations # isort:skip - -import pytest ; pytest - -#----------------------------------------------------------------------------- -# Imports -#----------------------------------------------------------------------------- - -# Bokeh imports -from bokeh.models import ( - ColumnDataSource, - CustomJS, - Plot, - Range1d, - RangeTool, - Rect, -) -from tests.support.plugins.project import SinglePlotPage -from tests.support.util.selenium import RECORD - -#----------------------------------------------------------------------------- -# Tests -#----------------------------------------------------------------------------- - -# TODO (bev) Add tests with y_range -# TODO (bev) Add tests with both x_range and y_range - -pytest_plugins = ( - "tests.support.plugins.project", -) - -def _make_plot(): - source = ColumnDataSource(dict(x=[1, 2], y=[1, 1])) - r = Range1d(start=0.4, end=0.6) - plot = Plot(height=400, width=1100, x_range=Range1d(0, 1), y_range=Range1d(0, 1), min_border=0) - plot.add_glyph(source, Rect(x='x', y='y', width=0.9, height=0.9)) - tool = RangeTool(x_range=r) - plot.add_tools(tool) - plot.min_border_right = 100 - code = RECORD("start", "t.x_range.start", final=False) + RECORD("end", "t.x_range.end") - plot.tags.append(CustomJS(name="custom-action", args=dict(t=tool), code=code)) - plot.toolbar_sticky = False - return plot - - -@pytest.mark.selenium -class Test_RangeTool: - def test_selected_by_default(self, single_plot_page: SinglePlotPage) -> None: - plot = _make_plot() - - page = single_plot_page(plot) - - [button] = page.get_toolbar_buttons(plot) - assert 'active' in button.get_attribute('class') - - assert page.has_no_console_errors() - - def test_can_be_deselected_and_selected(self, single_plot_page: SinglePlotPage) -> None: - plot = _make_plot() - - page = single_plot_page(plot) - - # Check is active - [button] = page.get_toolbar_buttons(plot) - assert 'active' in button.get_attribute('class') - - # Click and check is not active - [button] = page.get_toolbar_buttons(plot) - button.click() - assert 'active' not in button.get_attribute('class') - - # Click again and check is active - [button] = page.get_toolbar_buttons(plot) - button.click() - assert 'active' in button.get_attribute('class') - - assert page.has_no_console_errors() - - def test_center_pan_has_no_effect_when_deselected(self, single_plot_page: SinglePlotPage) -> None: - plot = _make_plot() - - page = single_plot_page(plot) - - [button] = page.get_toolbar_buttons(plot) - button.click() - - page.drag_canvas_at_position(plot, 500, 200, 100, 0) - - page.eval_custom_action() - - results = page.results - assert results['start'] == 0.4 - assert results['end'] == 0.6 - - assert page.has_no_console_errors() - - def test_center_pan_updates_range_when_selected(self, single_plot_page: SinglePlotPage) -> None: - plot = _make_plot() - - page = single_plot_page(plot) - - page.drag_canvas_at_position(plot, 500, 200, 100, 0) - - page.eval_custom_action() - - results = page.results - assert results['start'] == 0.5 - assert results['end'] == 0.7 - - page.drag_canvas_at_position(plot, 600, 200, -300, 0) - - page.eval_custom_action() - - results = page.results - assert results['start'] == 0.2 - assert results['end'] == 0.4 - - assert page.has_no_console_errors() - - def test_center_pan_with_right_side_outside(self, single_plot_page: SinglePlotPage) -> None: - plot = _make_plot() - plot.tools[0].x_range.end = 1.1 - - page = single_plot_page(plot) - - page.drag_canvas_at_position(plot, 500, 200, 100, 0) - - page.eval_custom_action() - - results = page.results - assert results['start'] == 0.5 - assert results['end'] == 1.2 - - page.drag_canvas_at_position(plot, 600, 200, -300, 0) - - page.eval_custom_action() - - results = page.results - assert results['start'] == 0.2 - assert results['end'] == 0.9 - - assert page.has_no_console_errors() - - def test_center_pan_with_left_side_outside(self, single_plot_page: SinglePlotPage) -> None: - plot = _make_plot() - plot.tools[0].x_range.start = -0.1 - - page = single_plot_page(plot) - - page.drag_canvas_at_position(plot, 500, 200, -100, 0) - - page.eval_custom_action() - - results = page.results - assert results['start'] == -0.2 - assert results['end'] == 0.5 - - page.drag_canvas_at_position(plot, 400, 200, 300, 0) - - page.eval_custom_action() - - results = page.results - assert results['start'] == 0.1 - assert results['end'] == 0.8 - - assert page.has_no_console_errors() - - def test_left_edge_drag_updates_start(self, single_plot_page: SinglePlotPage) -> None: - plot = _make_plot() - - page = single_plot_page(plot) - - page.drag_canvas_at_position(plot, 400, 200, 100, 0) - - page.eval_custom_action() - - results = page.results - assert results['start'] == 0.5 - assert results['end'] == 0.6 - - page.drag_canvas_at_position(plot, 500, 200, -300, 0) - - page.eval_custom_action() - - results = page.results - assert results['start'] == 0.2 - assert results['end'] == 0.6 - - def test_left_edge_drag_can_flip(self, single_plot_page: SinglePlotPage) -> None: - plot = _make_plot() - - page = single_plot_page(plot) - - page.drag_canvas_at_position(plot, 400, 200, 300, 0) - - page.eval_custom_action() - - results = page.results - assert results['start'] == 0.6 - assert results['end'] == 0.7 - - def test_left_edge_drag_with_right_edge_outside(self, single_plot_page: SinglePlotPage) -> None: - plot = _make_plot() - plot.tools[0].x_range.end = 1.1 - - page = single_plot_page(plot) - - page.drag_canvas_at_position(plot, 400, 200, 300, 0) - - page.eval_custom_action() - - results = page.results - assert results['start'] == 0.7 - assert results['end'] == 1.1 - - def test_right_edge_drag_updates_end(self, single_plot_page: SinglePlotPage) -> None: - plot = _make_plot() - - page = single_plot_page(plot) - - page.drag_canvas_at_position(plot, 600, 200, 100, 0) - - page.eval_custom_action() - - results = page.results - assert results['start'] == 0.4 - assert results['end'] == 0.7 - - page.drag_canvas_at_position(plot, 700, 200, -200, 0) - - page.eval_custom_action() - - results = page.results - assert results['start'] == 0.4 - assert results['end'] == 0.5 - - def test_right_edge_drag_can_flip(self, single_plot_page: SinglePlotPage) -> None: - plot = _make_plot() - - page = single_plot_page(plot) - - page.drag_canvas_at_position(plot, 600, 200, -300, 0) - - page.eval_custom_action() - - results = page.results - assert results['start'] == 0.3 - assert results['end'] == 0.4 - - def test_right_edge_drag_with_left_edge_outside(self, single_plot_page: SinglePlotPage) -> None: - plot = _make_plot() - plot.tools[0].x_range.start = -0.1 - - page = single_plot_page(plot) - - page.drag_canvas_at_position(plot, 600, 200, -300, 0) - - page.eval_custom_action() - - results = page.results - assert results['start'] == -0.1 - assert results['end'] == 0.3 - - def test_center_pan_stops_at_plot_range_limit(self, single_plot_page: SinglePlotPage) -> None: - plot = _make_plot() - - page = single_plot_page(plot) - - page.drag_canvas_at_position(plot, 500, 200, 300, 0) - - page.eval_custom_action() - - results = page.results - assert results['start'] == 0.7 - assert results['end'] == 0.9 - - page.drag_canvas_at_position(plot, 800, 200, 150, 0) - - page.eval_custom_action() - - results = page.results - assert results['start'] == 0.8 - assert results['end'] == 1 - - assert page.has_no_console_errors() diff --git a/tests/integration/tools/test_reset_tool.py b/tests/integration/tools/test_reset_tool.py deleted file mode 100644 index 21ad3967b59..00000000000 --- a/tests/integration/tools/test_reset_tool.py +++ /dev/null @@ -1,169 +0,0 @@ -#----------------------------------------------------------------------------- -# Copyright (c) Anaconda, Inc. All rights reserved. -# -# Powered by the Bokeh Development Team. -# -# The full license is in the file LICENSE.txt, distributed with this software. -#----------------------------------------------------------------------------- - -#----------------------------------------------------------------------------- -# Boilerplate -#----------------------------------------------------------------------------- -from __future__ import annotations # isort:skip - -import pytest ; pytest - -#----------------------------------------------------------------------------- -# Imports -#----------------------------------------------------------------------------- - -# Bokeh imports -from bokeh.events import RangesUpdate -from bokeh.models import ( - ColumnDataSource, - CustomJS, - Plot, - Range1d, - Rect, - ResetTool, - Scatter, - ZoomInTool, -) -from tests.support.plugins.project import SinglePlotPage -from tests.support.util.selenium import RECORD - -#----------------------------------------------------------------------------- -# Tests -#----------------------------------------------------------------------------- - -pytest_plugins = ( - "tests.support.plugins.project", -) - -def _make_plot(): - source = ColumnDataSource(dict(x=[1, 2], y=[1, 1])) - plot = Plot(height=400, width=400, x_range=Range1d(0, 1), y_range=Range1d(0, 1), min_border=0) - plot.add_glyph(source, Rect(x='x', y='y', width=0.9, height=0.9)) - plot.add_tools(ResetTool(), ZoomInTool()) - code = RECORD("xrstart", "p.x_range.start", final=False) + \ - RECORD("xrend", "p.x_range.end", final=False) + \ - RECORD("yrstart", "p.y_range.start", final=False) + \ - RECORD("yrend", "p.y_range.end") - plot.tags.append(CustomJS(name="custom-action", args=dict(p=plot), code=code)) - plot.toolbar_sticky = False - return plot - - -@pytest.mark.selenium -class Test_ResetTool: - def test_deselected_by_default(self, single_plot_page: SinglePlotPage) -> None: - plot = _make_plot() - - page = single_plot_page(plot) - - [reset, _zoom_in] = page.get_toolbar_buttons(plot) - assert 'active' not in reset.get_attribute('class') - - assert page.has_no_console_errors() - - def test_clicking_resets_range(self, single_plot_page: SinglePlotPage) -> None: - plot = _make_plot() - - page = single_plot_page(plot) - - # Change the ranges using a zoom in tool - [reset, zoom_in] = page.get_toolbar_buttons(plot) - zoom_in.click() - - page.eval_custom_action() - - results = page.results - assert results['xrstart'] != 0 - assert results['xrend'] != 1 - assert results['yrstart'] != 0 - assert results['yrend'] != 1 - - # Click the reset tool and check the ranges are restored - reset.click() - - page.eval_custom_action() - - results = page.results - assert results['xrstart'] == 0 - assert results['xrend'] == 1 - assert results['yrstart'] == 0 - assert results['yrend'] == 1 - - assert page.has_no_console_errors() - - def test_clicking_resets_selection(self, single_plot_page: SinglePlotPage) -> None: - source = ColumnDataSource(dict(x=[1, 2], y=[1, 1])) - source.selected.indices = [0] - source.selected.line_indices = [0] - - # XXX (bev) string key for multiline_indices seems questionable - source.selected.multiline_indices = {"0": [0]} - - plot = Plot(height=400, width=400, x_range=Range1d(0, 1), y_range=Range1d(0, 1), min_border=0) - plot.add_glyph(source, Scatter(x='x', y='y', size=20)) - plot.add_tools(ResetTool()) - code = \ - RECORD("indices", "s.selected.indices") + \ - RECORD("line_indices", "s.selected.line_indices") + \ - RECORD("multiline_indices", "s.selected.multiline_indices") - plot.tags.append(CustomJS(name="custom-action", args=dict(s=source), code=code)) - plot.toolbar_sticky = False - - page = single_plot_page(plot) - - # Verify selections are non empty - page.eval_custom_action() - - results = page.results - assert results['indices'] == [0] - assert results['line_indices'] == [0] - assert results['multiline_indices'] == {"0": [0]} # XXX (bev) string key - - # Click the reset tool and check the selections are restored - [reset] = page.get_toolbar_buttons(plot) - reset.click() - - page.eval_custom_action() - - results = page.results - assert results['indices'] == [] - assert results['line_indices'] == [] - assert results['multiline_indices'] == {} - - assert page.has_no_console_errors() - - def test_ranges_udpate(self, single_plot_page: SinglePlotPage) -> None: - source = ColumnDataSource(dict(x=[1, 2], y=[1, 1])) - plot = Plot(height=400, width=400, x_range=Range1d(0, 1), y_range=Range1d(0, 1), min_border=0) - plot.add_glyph(source, Rect(x='x', y='y', width=0.9, height=0.9)) - plot.add_tools(ResetTool(), ZoomInTool()) - code = RECORD("event_name", "cb_obj.event_name", final=False) + \ - RECORD("x0", "cb_obj.x0", final=False) + \ - RECORD("x1", "cb_obj.x1", final=False) + \ - RECORD("y0", "cb_obj.y0", final=False) + \ - RECORD("y1", "cb_obj.y1") - plot.js_on_event(RangesUpdate, CustomJS(code=code)) - plot.tags.append(CustomJS(name="custom-action", code="")) - plot.toolbar_sticky = False - - page = single_plot_page(plot) - - [reset, zoom_in] = page.get_toolbar_buttons(plot) - zoom_in.click() - reset.click() - - page.eval_custom_action() - - results = page.results - assert results['event_name'] == "rangesupdate" - assert results['x0'] == 0 - assert results['x1'] == 1 - assert results['y0'] == 0 - assert results['y1'] == 1 - - assert page.has_no_console_errors() diff --git a/tests/integration/tools/test_tap_tool.py b/tests/integration/tools/test_tap_tool.py deleted file mode 100644 index f6445d66784..00000000000 --- a/tests/integration/tools/test_tap_tool.py +++ /dev/null @@ -1,82 +0,0 @@ -#----------------------------------------------------------------------------- -# Copyright (c) Anaconda, Inc. All rights reserved. -# -# Powered by the Bokeh Development Team. -# -# The full license is in the file LICENSE.txt, distributed with this software. -#----------------------------------------------------------------------------- - -#----------------------------------------------------------------------------- -# Boilerplate -#----------------------------------------------------------------------------- -from __future__ import annotations # isort:skip - -import pytest ; pytest - -#----------------------------------------------------------------------------- -# Imports -#----------------------------------------------------------------------------- - -# Bokeh imports -from bokeh.models import CustomJS, TapTool -from bokeh.plotting import figure -from tests.support.plugins.project import SinglePlotPage -from tests.support.util.selenium import RECORD - -#----------------------------------------------------------------------------- -# Tests -#----------------------------------------------------------------------------- - -pytest_plugins = ( - "tests.support.plugins.project", -) - -# TODO (bev): -# -# check that .names is respected -# check that .renderers is respected - - -@pytest.mark.selenium -class Test_TapTool: - def test_tap_triggers_no_callback_without_hit(self, single_plot_page: SinglePlotPage) -> None: - plot = figure(height=800, width=1000, tools='') - plot.rect(x=[1, 2], y=[1, 1], width=1, height=1) - plot.add_tools(TapTool(callback=CustomJS(code=RECORD("indices", "cb_data.source.selected.indices")))) - plot.tags.append(CustomJS(name="custom-action", args=dict(p=plot), code=RECORD("junk", "10"))) - - page = single_plot_page(plot) - - # make sure no indicies (even an empty list) was recorded - page.click_canvas_at_position(plot, 50, 50) - page.eval_custom_action() - assert page.results == {"junk": 10} - - assert page.has_no_console_errors() - - def test_tap_triggers_callback_with_indices(self, single_plot_page: SinglePlotPage) -> None: - plot = figure(height=800, width=1000, tools='') - plot.rect(x=[1, 2], y=[1, 1], width=1, height=1) - plot.add_tools(TapTool(callback=CustomJS(code=RECORD("indices", "cb_data.source.selected.indices")))) - - page = single_plot_page(plot) - - page.click_canvas_at_position(plot, 400, 500) - assert page.results["indices"] == [0] - - page.click_canvas_at_position(plot, 600, 300) - assert page.results["indices"] == [1] - - assert page.has_no_console_errors() - - def test_tap_reports_all_indices_on_overlap(self, single_plot_page: SinglePlotPage) -> None: - plot = figure(height=800, width=1000, tools='') - plot.rect(x=[1, 1], y=[1, 1], width=1, height=1) - plot.add_tools(TapTool(callback=CustomJS(code=RECORD("indices", "cb_data.source.selected.indices")))) - - page = single_plot_page(plot) - - page.click_canvas_at_position(plot, 400, 500) - assert set(page.results["indices"]) == {0, 1} - - assert page.has_no_console_errors() diff --git a/tests/integration/tools/test_wheel_pan_tool.py b/tests/integration/tools/test_wheel_pan_tool.py deleted file mode 100644 index 448e3b57cae..00000000000 --- a/tests/integration/tools/test_wheel_pan_tool.py +++ /dev/null @@ -1,259 +0,0 @@ -#----------------------------------------------------------------------------- -# Copyright (c) Anaconda, Inc. All rights reserved. -# -# Powered by the Bokeh Development Team. -# -# The full license is in the file LICENSE.txt, distributed with this software. -#----------------------------------------------------------------------------- - -#----------------------------------------------------------------------------- -# Boilerplate -#----------------------------------------------------------------------------- -from __future__ import annotations # isort:skip - -import pytest ; pytest - -#----------------------------------------------------------------------------- -# Imports -#----------------------------------------------------------------------------- - -# Bokeh imports -from bokeh.events import RangesUpdate -from bokeh.models import ( - ColumnDataSource, - CustomJS, - Plot, - Range1d, - Rect, - WheelPanTool, -) -from tests.support.plugins.project import SinglePlotPage -from tests.support.util.selenium import RECORD, SCROLL - -#----------------------------------------------------------------------------- -# Tests -#----------------------------------------------------------------------------- - -pytest_plugins = ( - "tests.support.plugins.project", -) - -def _make_plot(dimension): - source = ColumnDataSource(dict(x=[1, 2], y=[1, 1])) - plot = Plot(height=400, width=400, x_range=Range1d(0, 1), y_range=Range1d(0, 1), min_border=0) - plot.add_glyph(source, Rect(x='x', y='y', width=0.9, height=0.9)) - plot.add_tools(WheelPanTool(dimension=dimension)) - code = RECORD("xrstart", "p.x_range.start", final=False) + \ - RECORD("xrend", "p.x_range.end", final=False) + \ - RECORD("yrstart", "p.y_range.start", final=False) + \ - RECORD("yrend", "p.y_range.end") - plot.tags.append(CustomJS(name="custom-action", args=dict(p=plot), code=code)) - plot.toolbar_sticky = False - return plot - - -@pytest.mark.selenium -class Test_WheelPanTool: - def test_xwheel_deselected_by_default(self, single_plot_page: SinglePlotPage) -> None: - plot = _make_plot('width') - - page = single_plot_page(plot) - - [button] = page.get_toolbar_buttons(plot) - assert 'active' not in button.get_attribute('class') - - assert page.has_no_console_errors() - - def test_xwheel_can_be_selected_and_deselected(self, single_plot_page: SinglePlotPage) -> None: - plot = _make_plot('width') - - page = single_plot_page(plot) - - # Check is not active - [button] = page.get_toolbar_buttons(plot) - assert 'active' not in button.get_attribute('class') - - # Click and check is active - [button] = page.get_toolbar_buttons(plot) - button.click() - assert 'active' in button.get_attribute('class') - - # Click again and check is not active - [button] = page.get_toolbar_buttons(plot) - button.click() - assert 'active' not in button.get_attribute('class') - - assert page.has_no_console_errors() - - def test_ywheel_deselected_by_default(self, single_plot_page: SinglePlotPage) -> None: - plot = _make_plot('height') - - page = single_plot_page(plot) - - [button] = page.get_toolbar_buttons(plot) - assert 'active' not in button.get_attribute('class') - - assert page.has_no_console_errors() - - def test_ywheel_can_be_selected_and_deselected(self, single_plot_page: SinglePlotPage) -> None: - plot = _make_plot('height') - - page = single_plot_page(plot) - - # Check is not active - [button] = page.get_toolbar_buttons(plot) - assert 'active' not in button.get_attribute('class') - - # Click and check is active - [button] = page.get_toolbar_buttons(plot) - button.click() - assert 'active' in button.get_attribute('class') - - # Click again and check is not active - [button] = page.get_toolbar_buttons(plot) - button.click() - assert 'active' not in button.get_attribute('class') - - assert page.has_no_console_errors() - - def test_xwheel_pan(self, single_plot_page: SinglePlotPage) -> None: - plot = _make_plot('width') - - page = single_plot_page(plot) - - # First check that scrolling has no effect before the tool is activated - page.driver.execute_script(SCROLL(-200)) - - page.eval_custom_action() - - results = page.results - assert results['xrstart'] == 0 - assert results['xrend'] == 1 - assert results['yrstart'] == 0 - assert results['yrend'] == 1 - - # Next check that scrolling adjusts the x range after the tool is activated - [button] = page.get_toolbar_buttons(plot) - button.click() - - page.driver.execute_script(SCROLL(-200)) - - page.eval_custom_action() - - results = page.results - assert results['xrstart'] < 0 - assert results['xrend'] < 1 - assert results['yrstart'] == 0 - assert results['yrend'] == 1 - - page.driver.execute_script(SCROLL(400)) - - page.eval_custom_action() - - results = page.results - assert results['xrstart'] > 0 - assert results['xrend'] > 1 - assert results['yrstart'] == 0 - assert results['yrend'] == 1 - - assert page.has_no_console_errors() - - def test_ywheel_pan(self, single_plot_page: SinglePlotPage) -> None: - plot = _make_plot("height") - - page = single_plot_page(plot) - - # First check that scrolling has no effect before the tool is activated - page.driver.execute_script(SCROLL(-200)) - - page.eval_custom_action() - - results = page.results - assert results['xrstart'] == 0 - assert results['xrend'] == 1 - assert results['yrstart'] == 0 - assert results['yrend'] == 1 - - # Next check that scrolling adjusts the y range after the tool is activated - [button] = page.get_toolbar_buttons(plot) - button.click() - - page.driver.execute_script(SCROLL(-200)) - - page.eval_custom_action() - - results = page.results - assert results['xrstart'] == 0 - assert results['xrend'] == 1 - assert results['yrstart'] > 0 - assert results['yrend'] > 1 - - page.driver.execute_script(SCROLL(400)) - - page.eval_custom_action() - - results = page.results - assert results['xrstart'] == 0 - assert results['xrend'] == 1 - assert results['yrstart'] < 0 - assert results['yrend'] < 1 - - assert page.has_no_console_errors() - - def test_xpan_ranges_update(self, single_plot_page: SinglePlotPage) -> None: - source = ColumnDataSource(dict(x=[1, 2], y=[1, 1])) - plot = Plot(height=400, width=400, x_range=Range1d(0, 1), y_range=Range1d(0, 1), min_border=0) - plot.add_glyph(source, Rect(x='x', y='y', width=0.9, height=0.9)) - plot.add_tools(WheelPanTool(dimension='width')) - code = RECORD("event_name", "cb_obj.event_name", final=False) + \ - RECORD("x0", "cb_obj.x0", final=False) + \ - RECORD("x1", "cb_obj.x1", final=False) + \ - RECORD("y0", "cb_obj.y0", final=False) + \ - RECORD("y1", "cb_obj.y1") - plot.js_on_event(RangesUpdate, CustomJS(code=code)) - plot.tags.append(CustomJS(name="custom-action", code="")) - plot.toolbar_sticky = False - - page = single_plot_page(plot) - - [button] = page.get_toolbar_buttons(plot) - button.click() - page.driver.execute_script(SCROLL(-200)) - page.eval_custom_action() - results = page.results - assert results['event_name'] == "rangesupdate" - assert results['x0'] < 0 - assert results['x1'] < 1 - assert results['y0'] == 0 - assert results['y1'] == 1 - - assert page.has_no_console_errors() - - def test_ypan_ranges_update(self, single_plot_page: SinglePlotPage) -> None: - source = ColumnDataSource(dict(x=[1, 2], y=[1, 1])) - plot = Plot(height=400, width=400, x_range=Range1d(0, 1), y_range=Range1d(0, 1), min_border=0) - plot.add_glyph(source, Rect(x='x', y='y', width=0.9, height=0.9)) - plot.add_tools(WheelPanTool(dimension='height')) - code = RECORD("event_name", "cb_obj.event_name", final=False) + \ - RECORD("x0", "cb_obj.x0", final=False) + \ - RECORD("x1", "cb_obj.x1", final=False) + \ - RECORD("y0", "cb_obj.y0", final=False) + \ - RECORD("y1", "cb_obj.y1") - plot.js_on_event(RangesUpdate, CustomJS(code=code)) - plot.tags.append(CustomJS(name="custom-action", code="")) - plot.toolbar_sticky = False - - page = single_plot_page(plot) - - [button] = page.get_toolbar_buttons(plot) - button.click() - page.driver.execute_script(SCROLL(-200)) - page.eval_custom_action() - results = page.results - assert results['event_name'] == "rangesupdate" - assert results['x0'] == 0 - assert results['x1'] == 1 - assert results['y0'] > 0 - assert results['y1'] > 1 - - assert page.has_no_console_errors() diff --git a/tests/integration/tools/test_wheel_zoom_tool.py b/tests/integration/tools/test_wheel_zoom_tool.py deleted file mode 100644 index 86ec8d5a4ba..00000000000 --- a/tests/integration/tools/test_wheel_zoom_tool.py +++ /dev/null @@ -1,266 +0,0 @@ -#----------------------------------------------------------------------------- -# Copyright (c) Anaconda, Inc. All rights reserved. -# -# Powered by the Bokeh Development Team. -# -# The full license is in the file LICENSE.txt, distributed with this software. -#----------------------------------------------------------------------------- - -#----------------------------------------------------------------------------- -# Boilerplate -#----------------------------------------------------------------------------- -from __future__ import annotations # isort:skip - -import pytest ; pytest - -#----------------------------------------------------------------------------- -# Imports -#----------------------------------------------------------------------------- - -# Bokeh imports -from bokeh.events import RangesUpdate -from bokeh.models import ( - ColumnDataSource, - CustomJS, - Plot, - Range1d, - Rect, - WheelZoomTool, -) -from tests.support.plugins.project import SinglePlotPage -from tests.support.util.selenium import RECORD, SCROLL - -#----------------------------------------------------------------------------- -# Tests -#----------------------------------------------------------------------------- - -pytest_plugins = ( - "tests.support.plugins.project", -) - -def _make_plot(dimensions="both"): - source = ColumnDataSource(dict(x=[1, 2], y=[1, 1])) - plot = Plot(height=400, width=400, x_range=Range1d(0, 1), y_range=Range1d(0, 1), min_border=0) - plot.add_glyph(source, Rect(x='x', y='y', width=0.9, height=0.9)) - plot.add_tools(WheelZoomTool(dimensions=dimensions)) - code = RECORD("xrstart", "p.x_range.start", final=False) + \ - RECORD("xrend", "p.x_range.end", final=False) + \ - RECORD("yrstart", "p.y_range.start", final=False) + \ - RECORD("yrend", "p.y_range.end") - plot.tags.append(CustomJS(name="custom-action", args=dict(p=plot), code=code)) - plot.toolbar_sticky = False - return plot - - -@pytest.mark.selenium -class Test_WheelZoomTool: - def test_deselected_by_default(self, single_plot_page: SinglePlotPage) -> None: - plot = _make_plot() - - page = single_plot_page(plot) - - [button] = page.get_toolbar_buttons(plot) - assert 'active' not in button.get_attribute('class') - - assert page.has_no_console_errors() - - def test_can_be_selected_and_deselected(self, single_plot_page: SinglePlotPage) -> None: - plot = _make_plot() - - page = single_plot_page(plot) - - # Check is not active - [button] = page.get_toolbar_buttons(plot) - assert 'active' not in button.get_attribute('class') - - # Click and check is active - [button] = page.get_toolbar_buttons(plot) - button.click() - assert 'active' in button.get_attribute('class') - - # Click again and check is not active - [button] = page.get_toolbar_buttons(plot) - button.click() - assert 'active' not in button.get_attribute('class') - - assert page.has_no_console_errors() - - def test_zoom_out(self, single_plot_page: SinglePlotPage) -> None: - plot = _make_plot() - - page = single_plot_page(plot) - - # First check that scrolling has no effect before the tool is activated - page.driver.execute_script(SCROLL(200)) - - page.eval_custom_action() - - results = page.results - assert results['xrstart'] == 0 - assert results['xrend'] == 1 - assert results['yrstart'] == 0 - assert results['yrend'] == 1 - - # Next check that scrolling adjusts the range after the tool is activated - [button] = page.get_toolbar_buttons(plot) - button.click() - - page.driver.execute_script(SCROLL(200)) - - page.eval_custom_action() - - results = page.results - assert results['xrstart'] < 0 - assert results['xrend'] > 1 - assert results['yrstart'] < 0 - assert results['yrend'] > 1 - - assert page.has_no_console_errors() - - def test_zoom_in(self, single_plot_page: SinglePlotPage) -> None: - plot = _make_plot() - - page = single_plot_page(plot) - - # First check that scrolling has no effect before the tool is activated - page.driver.execute_script(SCROLL(-200)) - - page.eval_custom_action() - - results = page.results - assert results['xrstart'] == 0 - assert results['xrend'] == 1 - assert results['yrstart'] == 0 - assert results['yrend'] == 1 - - # Next check that scrolling adjusts the range after the tool is activated - [button] = page.get_toolbar_buttons(plot) - button.click() - - page.driver.execute_script(SCROLL(-200)) - - page.eval_custom_action() - - results = page.results - assert results['xrstart'] > 0 - assert results['xrend'] < 1 - assert results['yrstart'] > 0 - assert results['yrend'] < 1 - - assert page.has_no_console_errors() - - def test_xwheel_zoom(self, single_plot_page: SinglePlotPage) -> None: - plot = _make_plot(dimensions="width") - - page = single_plot_page(plot) - - # First check that scrolling has no effect before the tool is activated - page.driver.execute_script(SCROLL(-200)) - - page.eval_custom_action() - - results = page.results - assert results['xrstart'] == 0 - assert results['xrend'] == 1 - assert results['yrstart'] == 0 - assert results['yrend'] == 1 - - # Next check that scrolling adjusts the x range after the tool is activated - [button] = page.get_toolbar_buttons(plot) - button.click() - - page.driver.execute_script(SCROLL(-200)) - - page.eval_custom_action() - - results = page.results - assert results['xrstart'] > 0 - assert results['xrend'] < 1 - assert results['yrstart'] == 0 - assert results['yrend'] == 1 - - page.driver.execute_script(SCROLL(400)) - - page.eval_custom_action() - - results = page.results - assert results['xrstart'] < 0 - assert results['xrend'] > 1 - assert results['yrstart'] == 0 - assert results['yrend'] == 1 - - assert page.has_no_console_errors() - - def test_ywheel_zoom(self, single_plot_page: SinglePlotPage) -> None: - plot = _make_plot(dimensions="height") - - page = single_plot_page(plot) - - # First check that scrolling has no effect before the tool is activated - page.driver.execute_script(SCROLL(-200)) - - page.eval_custom_action() - - results = page.results - assert results['xrstart'] == 0 - assert results['xrend'] == 1 - assert results['yrstart'] == 0 - assert results['yrend'] == 1 - - # Next check that scrolling adjusts the y range after the tool is activated - [button] = page.get_toolbar_buttons(plot) - button.click() - - page.driver.execute_script(SCROLL(-200)) - - page.eval_custom_action() - - results = page.results - assert results['xrstart'] == 0 - assert results['xrend'] == 1 - assert results['yrstart'] > 0 - assert results['yrend'] < 1 - - page.driver.execute_script(SCROLL(400)) - - page.eval_custom_action() - - results = page.results - assert results['xrstart'] == 0 - assert results['xrend'] == 1 - assert results['yrstart'] < 0 - assert results['yrend'] > 1 - - assert page.has_no_console_errors() - - def test_ranges_update(self, single_plot_page: SinglePlotPage) -> None: - source = ColumnDataSource(dict(x=[1, 2], y=[1, 1])) - plot = Plot(height=400, width=400, x_range=Range1d(0, 1), y_range=Range1d(0, 1), min_border=0) - plot.add_glyph(source, Rect(x='x', y='y', width=0.9, height=0.9)) - plot.add_tools(WheelZoomTool()) - code = RECORD("event_name", "cb_obj.event_name", final=False) + \ - RECORD("x0", "cb_obj.x0", final=False) + \ - RECORD("x1", "cb_obj.x1", final=False) + \ - RECORD("y0", "cb_obj.y0", final=False) + \ - RECORD("y1", "cb_obj.y1") - plot.js_on_event(RangesUpdate, CustomJS(code=code)) - plot.tags.append(CustomJS(name="custom-action", code="")) - plot.toolbar_sticky = False - - page = single_plot_page(plot) - - [button] = page.get_toolbar_buttons(plot) - button.click() - - page.driver.execute_script(SCROLL(-200)) - - page.eval_custom_action() - - results = page.results - assert results['event_name'] == "rangesupdate" - assert results['x0'] > 0 - assert results['x1'] < 1 - assert results['y0'] > 0 - assert results['y1'] < 1 - - assert page.has_no_console_errors() diff --git a/tests/integration/tools/test_zoom_in_tool.py b/tests/integration/tools/test_zoom_in_tool.py deleted file mode 100644 index bcfbd1246d8..00000000000 --- a/tests/integration/tools/test_zoom_in_tool.py +++ /dev/null @@ -1,124 +0,0 @@ -#----------------------------------------------------------------------------- -# Copyright (c) Anaconda, Inc. All rights reserved. -# -# Powered by the Bokeh Development Team. -# -# The full license is in the file LICENSE.txt, distributed with this software. -#----------------------------------------------------------------------------- - -#----------------------------------------------------------------------------- -# Boilerplate -#----------------------------------------------------------------------------- -from __future__ import annotations # isort:skip - -import pytest ; pytest - -#----------------------------------------------------------------------------- -# Imports -#----------------------------------------------------------------------------- - -# Bokeh imports -from bokeh.events import RangesUpdate -from bokeh.models import ( - ColumnDataSource, - CustomJS, - Plot, - Range1d, - Rect, - ZoomInTool, -) -from tests.support.plugins.project import SinglePlotPage -from tests.support.util.selenium import RECORD - -#----------------------------------------------------------------------------- -# Tests -#----------------------------------------------------------------------------- - -pytest_plugins = ( - "tests.support.plugins.project", -) - -def _make_plot(): - source = ColumnDataSource(dict(x=[1, 2], y=[1, 1])) - plot = Plot(height=400, width=400, x_range=Range1d(0, 1), y_range=Range1d(0, 1), min_border=0) - plot.add_glyph(source, Rect(x='x', y='y', width=0.9, height=0.9)) - plot.add_tools(ZoomInTool()) - code = RECORD("xrstart", "p.x_range.start", final=False) + \ - RECORD("xrend", "p.x_range.end", final=False) + \ - RECORD("yrstart", "p.y_range.start", final=False) + \ - RECORD("yrend", "p.y_range.end") - plot.tags.append(CustomJS(name="custom-action", args=dict(p=plot), code=code)) - plot.toolbar_sticky = False - return plot - - -@pytest.mark.selenium -class Test_ZoomInTool: - def test_deselected_by_default(self, single_plot_page: SinglePlotPage) -> None: - plot = _make_plot() - - page = single_plot_page(plot) - - [button] = page.get_toolbar_buttons(plot) - assert 'active' not in button.get_attribute('class') - - assert page.has_no_console_errors() - - def test_clicking_zooms_in(self, single_plot_page: SinglePlotPage) -> None: - plot = _make_plot() - - page = single_plot_page(plot) - - [button] = page.get_toolbar_buttons(plot) - button.click() - - page.eval_custom_action() - - first = page.results - assert first['xrstart'] > 0 - assert first['xrend'] < 1 - assert first['yrstart'] > 0 - assert first['yrend'] < 1 - - [button] = page.get_toolbar_buttons(plot) - button.click() - - page.eval_custom_action() - - second = page.results - assert second['xrstart'] > first['xrstart'] - assert second['xrend'] < first['xrend'] - assert second['yrstart'] > first['yrstart'] - assert second['yrend'] < first['yrend'] - - assert page.has_no_console_errors() - - def test_ranges_udpate(self, single_plot_page: SinglePlotPage) -> None: - source = ColumnDataSource(dict(x=[1, 2], y=[1, 1])) - plot = Plot(height=400, width=400, x_range=Range1d(0, 1), y_range=Range1d(0, 1), min_border=0) - plot.add_glyph(source, Rect(x='x', y='y', width=0.9, height=0.9)) - plot.add_tools(ZoomInTool()) - code = RECORD("event_name", "cb_obj.event_name", final=False) + \ - RECORD("x0", "cb_obj.x0", final=False) + \ - RECORD("x1", "cb_obj.x1", final=False) + \ - RECORD("y0", "cb_obj.y0", final=False) + \ - RECORD("y1", "cb_obj.y1") - plot.js_on_event(RangesUpdate, CustomJS(code=code)) - plot.tags.append(CustomJS(name="custom-action", code="")) - plot.toolbar_sticky = False - - page = single_plot_page(plot) - - [button] = page.get_toolbar_buttons(plot) - button.click() - - page.eval_custom_action() - - results = page.results - assert results['event_name'] == "rangesupdate" - assert results['x0'] > 0 - assert results['x1'] < 1 - assert results['y0'] > 0 - assert results['y1'] < 1 - - assert page.has_no_console_errors() diff --git a/tests/integration/tools/test_zoom_out_tool.py b/tests/integration/tools/test_zoom_out_tool.py deleted file mode 100644 index 5320c46dfd7..00000000000 --- a/tests/integration/tools/test_zoom_out_tool.py +++ /dev/null @@ -1,124 +0,0 @@ -#----------------------------------------------------------------------------- -# Copyright (c) Anaconda, Inc. All rights reserved. -# -# Powered by the Bokeh Development Team. -# -# The full license is in the file LICENSE.txt, distributed with this software. -#----------------------------------------------------------------------------- - -#----------------------------------------------------------------------------- -# Boilerplate -#----------------------------------------------------------------------------- -from __future__ import annotations # isort:skip - -import pytest ; pytest - -#----------------------------------------------------------------------------- -# Imports -#----------------------------------------------------------------------------- - -# Bokeh imports -from bokeh.events import RangesUpdate -from bokeh.models import ( - ColumnDataSource, - CustomJS, - Plot, - Range1d, - Rect, - ZoomOutTool, -) -from tests.support.plugins.project import SinglePlotPage -from tests.support.util.selenium import RECORD - -#----------------------------------------------------------------------------- -# Tests -#----------------------------------------------------------------------------- - -pytest_plugins = ( - "tests.support.plugins.project", -) - -def _make_plot(): - source = ColumnDataSource(dict(x=[1, 2], y=[1, 1])) - plot = Plot(height=400, width=400, x_range=Range1d(0, 1), y_range=Range1d(0, 1), min_border=0) - plot.add_glyph(source, Rect(x='x', y='y', width=0.9, height=0.9)) - plot.add_tools(ZoomOutTool()) - code = RECORD("xrstart", "p.x_range.start", final=False) + \ - RECORD("xrend", "p.x_range.end", final=False) + \ - RECORD("yrstart", "p.y_range.start", final=False) + \ - RECORD("yrend", "p.y_range.end") - plot.tags.append(CustomJS(name="custom-action", args=dict(p=plot), code=code)) - plot.toolbar_sticky = False - return plot - - -@pytest.mark.selenium -class Test_ZoomOutTool: - def test_deselected_by_default(self, single_plot_page: SinglePlotPage) -> None: - plot = _make_plot() - - page = single_plot_page(plot) - - [button] = page.get_toolbar_buttons(plot) - assert 'active' not in button.get_attribute('class') - - assert page.has_no_console_errors() - - def test_clicking_zooms_out(self, single_plot_page: SinglePlotPage) -> None: - plot = _make_plot() - - page = single_plot_page(plot) - - [button] = page.get_toolbar_buttons(plot) - button.click() - - page.eval_custom_action() - - first = page.results - assert first['xrstart'] < 0 - assert first['xrend'] > 1 - assert first['yrstart'] < 0 - assert first['yrend'] > 1 - - [button] = page.get_toolbar_buttons(plot) - button.click() - - page.eval_custom_action() - - second = page.results - assert second['xrstart'] < first['xrstart'] - assert second['xrend'] > first['xrend'] - assert second['yrstart'] < first['yrstart'] - assert second['yrend'] > first['yrend'] - - assert page.has_no_console_errors() - - def test_ranges_udpate(self, single_plot_page: SinglePlotPage) -> None: - source = ColumnDataSource(dict(x=[1, 2], y=[1, 1])) - plot = Plot(height=400, width=400, x_range=Range1d(0, 1), y_range=Range1d(0, 1), min_border=0) - plot.add_glyph(source, Rect(x='x', y='y', width=0.9, height=0.9)) - plot.add_tools(ZoomOutTool()) - code = RECORD("event_name", "cb_obj.event_name", final=False) + \ - RECORD("x0", "cb_obj.x0", final=False) + \ - RECORD("x1", "cb_obj.x1", final=False) + \ - RECORD("y0", "cb_obj.y0", final=False) + \ - RECORD("y1", "cb_obj.y1") - plot.js_on_event(RangesUpdate, CustomJS(code=code)) - plot.tags.append(CustomJS(name="custom-action", code="")) - plot.toolbar_sticky = False - - page = single_plot_page(plot) - - [button] = page.get_toolbar_buttons(plot) - button.click() - - page.eval_custom_action() - - results = page.results - assert results['event_name'] == "rangesupdate" - assert results['x0'] < 0 - assert results['x1'] > 1 - assert results['y0'] < 0 - assert results['y1'] > 1 - - assert page.has_no_console_errors() diff --git a/tests/integration/widgets/__init__.py b/tests/integration/widgets/__init__.py deleted file mode 100644 index e69de29bb2d..00000000000 diff --git a/tests/integration/widgets/tables/__init__.py b/tests/integration/widgets/tables/__init__.py deleted file mode 100644 index e69de29bb2d..00000000000 diff --git a/tests/integration/widgets/tables/test_cell_editors.py b/tests/integration/widgets/tables/test_cell_editors.py deleted file mode 100644 index 2004081603b..00000000000 --- a/tests/integration/widgets/tables/test_cell_editors.py +++ /dev/null @@ -1,422 +0,0 @@ -#----------------------------------------------------------------------------- -# Copyright (c) Anaconda, Inc. All rights reserved. -# -# Powered by the Bokeh Development Team. -# -# The full license is in the file LICENSE.txt, distributed with this software. -#----------------------------------------------------------------------------- - -#----------------------------------------------------------------------------- -# Boilerplate -#----------------------------------------------------------------------------- -from __future__ import annotations # isort:skip - -import pytest ; pytest - -#----------------------------------------------------------------------------- -# Imports -#----------------------------------------------------------------------------- - -# Standard library imports -from time import sleep - -# Bokeh imports -from bokeh.models import ( - ColumnDataSource, - CustomJS, - DataTable, - IntEditor, - NumberEditor, - StringEditor, - TableColumn, -) -from tests.support.plugins.project import BokehModelPage -from tests.support.util.selenium import ( - RECORD, - enter_text_in_cell, - enter_text_in_cell_with_click_enter, - escape_cell, - get_table_cell, -) - -#----------------------------------------------------------------------------- -# Tests -#----------------------------------------------------------------------------- - -pytest_plugins = ( - "tests.support.plugins.project", -) - - -def make_table(editor, values): - source = ColumnDataSource({'values': values}) - column = TableColumn(field='values', title='values', editor=editor()) - table = DataTable(source=source, columns=[column], editable=True, width=600) - # this is triggered on selection changes - source.selected.js_on_change('indices', CustomJS(args=dict(s=source), code=RECORD("values", "s.data.values"))) - return table - -# XXX Checkbox editor is currently completely broken -# @pytest.mark.selenium -# class Test_CheckboxEditor: - -# values = [True, False] -# editor = CheckboxEditor - -# def test_editing_does_not_update_source_on_noneditable_table(self, bokeh_model_page: BokehModelPage) -> None: -# table = make_table() -# table.editable = False -# page = bokeh_model_page(table) - -# # Click row 1 (which triggers the selection callback) -# cell = get_table_cell(page.driver, table, 1, 1) -# cell.click() -# results = page.results -# assert results['values'] == self.values - -# # Now double click, enter the text new value and -# cell = get_table_cell(page.driver, table, 1, 1) -# # TODO - -# # Click row 2 (which triggers callback again so we can inspect the data) -# cell = get_table_cell(page.driver, table, 2, 1) -# cell.click() -# results = page.results -# assert results['values'] == self.values - -# assert page.has_no_console_errors() - -# def test_editing_updates_source(self, bokeh_model_page: BokehModelPage) -> None: -# table = make_table() -# page = bokeh_model_page(table) - -# # Click row 1 (which triggers the selection callback) -# cell = get_table_cell(page.driver, table, 1, 1) -# cell.click() -# results = page.results -# assert results['values'] == self.values - -# # Now double click, enter the text new value and -# cell = get_table_cell(page.driver, table, 1, 1) -# # TODO - -# # Click row 2 (which triggers callback again so we can inspect the data) -# cell = get_table_cell(page.driver, table, 2, 1) -# cell.click() -# results = page.results -# assert results['values'] == [False, False] - -# assert page.has_no_console_errors() - -@pytest.mark.selenium -class Test_IntEditor: - - values = [1, 2] - editor = IntEditor - - def test_editing_does_not_update_source_on_noneditable_table(self, bokeh_model_page: BokehModelPage) -> None: - table = make_table(self.editor, self.values) - table.editable = False - page = bokeh_model_page(table) - - # Click row 1 (which triggers the selection callback) - cell = get_table_cell(page.driver, table, 1, 1) - cell.click() - results = page.results - assert results['values'] == self.values - - # Now double click, enter the text new value and - enter_text_in_cell(page.driver, table, 1, 1, "33") - - # Click row 2 (which triggers callback again so we can inspect the data) - cell = get_table_cell(page.driver, table, 2, 1) - cell.click() - results = page.results - assert results['values'] == self.values - - assert page.has_no_console_errors() - - @pytest.mark.parametrize('bad', ["1.1", "text"]) - def test_editing_does_not_update_source_on_bad_values(self, bad: str, bokeh_model_page: BokehModelPage) -> None: - table = make_table(self.editor, self.values) - page = bokeh_model_page(table) - - # Click row 1 (which triggers the selection callback) - cell = get_table_cell(page.driver, table, 1, 1) - cell.click() - results = page.results - assert results['values'] == self.values - - # Now double click, enter the text new value and - enter_text_in_cell(page.driver, table, 1, 1, bad) - escape_cell(page.driver, table, 1, 1) - - # Click row 2 (which triggers callback again so we can inspect the data) - cell = get_table_cell(page.driver, table, 2, 1) - cell.click() - - results = page.results - assert results['values'] == self.values - - assert page.has_no_console_errors() - - def test_editing_updates_source(self, bokeh_model_page: BokehModelPage) -> None: - table = make_table(self.editor, self.values) - page = bokeh_model_page(table) - - # Click row 1 (which triggers the selection callback) - cell = get_table_cell(page.driver, table, 1, 1) - cell.click() - results = page.results - assert results['values'] == self.values - - # Now double click, enter the text new value and - enter_text_in_cell(page.driver, table, 1, 1, "33") - - # Click row 2 (which triggers callback again so we can inspect the data) - cell = get_table_cell(page.driver, table, 2, 1) - cell.click() - sleep(0.5) - results = page.results - assert results['values'] == [33, 2] - - assert page.has_no_console_errors() - -@pytest.mark.selenium -class Test_NumberEditor: - - values = [1.1, 2.2] - editor = NumberEditor - - def test_editing_does_not_update_source_on_noneditable_table(self, bokeh_model_page: BokehModelPage) -> None: - table = make_table(self.editor, self.values) - table.editable = False - page = bokeh_model_page(table) - - # Click row 1 (which triggers the selection callback) - cell = get_table_cell(page.driver, table, 1, 1) - cell.click() - results = page.results - assert results['values'] == self.values - - # Now double click, enter the text new value and - enter_text_in_cell(page.driver, table, 1, 1, "33.5") - - # Click row 2 (which triggers callback again so we can inspect the data) - cell = get_table_cell(page.driver, table, 2, 1) - cell.click() - results = page.results - assert results['values'] == self.values - - assert page.has_no_console_errors() - - @pytest.mark.parametrize('bad', ["text"]) - def test_editing_does_not_update_source_on_bad_values(self, bad, bokeh_model_page: BokehModelPage) -> None: - table = make_table(self.editor, self.values) - page = bokeh_model_page(table) - - # Click row 1 (which triggers the selection callback) - cell = get_table_cell(page.driver, table, 1, 1) - cell.click() - results = page.results - assert results['values'] == self.values - - # Now double click, enter the text new value and - enter_text_in_cell(page.driver, table, 1, 1, bad) - escape_cell(page.driver, table, 1, 1) - - # Click row 2 (which triggers callback again so we can inspect the data) - cell = get_table_cell(page.driver, table, 2, 1) - cell.click() - - results = page.results - assert results['values'] == self.values - - assert page.has_no_console_errors() - - def test_editing_updates_source(self, bokeh_model_page: BokehModelPage) -> None: - table = make_table(self.editor, self.values) - page = bokeh_model_page(table) - - # Click row 1 (which triggers the selection callback) - cell = get_table_cell(page.driver, table, 1, 1) - cell.click() - results = page.results - assert results['values'] == self.values - - # Now double click, enter the text new value and - enter_text_in_cell(page.driver, table, 1, 1, "33.5") - - # Click row 2 (which triggers callback again so we can inspect the data) - cell = get_table_cell(page.driver, table, 2, 1) - cell.click() - results = page.results - assert results['values'] == [33.5, 2.2] - - assert page.has_no_console_errors() - -@pytest.mark.selenium -class Test_StringEditor: - - values = ["foo", "bar"] - editor = StringEditor - - def test_editing_does_not_update_source_on_noneditable_table(self, bokeh_model_page: BokehModelPage) -> None: - table = make_table(self.editor, self.values) - table.editable = False - page = bokeh_model_page(table) - - # Click row 1 (which triggers the selection callback) - cell = get_table_cell(page.driver, table, 1, 1) - cell.click() - results = page.results - assert results['values'] == self.values - - # Now double click, enter the text new value and - enter_text_in_cell(page.driver, table, 1, 1, "baz") - - # Click row 2 (which triggers callback again so we can inspect the data) - cell = get_table_cell(page.driver, table, 2, 1) - cell.click() - results = page.results - assert results['values'] == self.values - - assert page.has_no_console_errors() - - # XXX: how are those bad values for a **string**? - #@pytest.mark.parametrize('bad', ["1", "1.1", "-1"]) - #def test_editing_does_not_update_source_on_bad_values(self, bad, bokeh_model_page: BokehModelPage) -> None: - # table = make_table(self.editor, self.values) - # page = bokeh_model_page(table) - - # # Click row 1 (which triggers the selection callback) - # cell = get_table_cell(page.driver, table, 1, 1) - # cell.click() - # results = page.results - # assert results['values'] == self.values - - # # Now double click, enter the text new value and - # enter_text_in_cell(page.driver, table, 1, 1, bad) - - # # Click row 2 (which triggers callback again so we can inspect the data) - # cell = get_table_cell(page.driver, table, 2, 1) - # cell.click() - # results = page.results - # assert results['values'] == self.values - - # assert page.has_no_console_errors() - - def test_editing_updates_source(self, bokeh_model_page: BokehModelPage) -> None: - table = make_table(self.editor, self.values) - page = bokeh_model_page(table) - - # Click row 1 (which triggers the selection callback) - cell = get_table_cell(page.driver, table, 1, 1) - cell.click() - results = page.results - assert results['values'] == self.values - - # Now double click, enter the text new value and - enter_text_in_cell(page.driver, table, 1, 1, "baz") - - # Click row 2 (which triggers callback again so we can inspect the data) - cell = get_table_cell(page.driver, table, 2, 1) - cell.click() - results = page.results - assert results['values'] == ["baz", "bar"] - - assert page.has_no_console_errors() - - def test_editing_updates_source_with_click_enter(self, bokeh_model_page: BokehModelPage) -> None: - table = make_table(self.editor, self.values) - page = bokeh_model_page(table) - - # Click row 1 (which triggers the selection callback) - cell = get_table_cell(page.driver, table, 1, 1) - cell.click() - results = page.results - assert results['values'] == self.values - - # Now double click, enter the text new value and - enter_text_in_cell_with_click_enter(page.driver, table, 1, 1, "baz") - - # Click row 2 (which triggers callback again so we can inspect the data) - cell = get_table_cell(page.driver, table, 2, 1) - cell.click() - results = page.results - assert results['values'] == ["baz", "bar"] - - assert page.has_no_console_errors() - -# XXX (bev) PercentEditor is currently completely broken -# @pytest.mark.selenium -# class Test_PercentEditor: - -# values = [0.1, 0.2] -# editor = PercentEditor - -# def test_editing_does_not_update_source_on_noneditable_table(self, bokeh_model_page: BokehModelPage) -> None: -# table = make_table() -# table.editable = False -# page = bokeh_model_page(table) - -# # Click row 1 (which triggers the selection callback) -# cell = get_table_cell(page.driver, table, 1, 1) -# cell.click() -# results = page.results -# assert results['values'] == self.values - -# # Now double click, enter the text 33 and -# enter_text_in_cell(page.driver, table, 1, 1, "0.5") - -# # Click row 2 (which triggers callback again so we can inspect the data) -# cell = get_table_cell(page.driver, table, 2, 1) -# cell.click() -# results = page.results -# assert results['values'] == self.values - -# assert page.has_no_console_errors() - -# @pytest.mark.parametrize('bad', ["-1", "-0.5", "1.1", "2", "text"]) -# def test_editing_does_not_update_source_on_bad_values(self, bad, bokeh_model_page: BokehModelPage) -> None: -# table = make_table() -# table.editable = False -# page = bokeh_model_page(table) - -# # Click row 1 (which triggers the selection callback) -# cell = get_table_cell(page.driver, table, 1, 1) -# cell.click() -# results = page.results -# assert results['values'] == self.values - -# # Now double click, enter the text new value and -# enter_text_in_cell(page.driver, table, 1, 1, bad) - -# # Click row 2 (which triggers callback again so we can inspect the data) -# cell = get_table_cell(page.driver, table, 2, 1) -# cell.click() -# results = page.results -# assert results['values'] == self.values - -# assert page.has_no_console_errors() - -# def test_editing_updates_source(self, bokeh_model_page: BokehModelPage) -> None: -# table = make_table() -# page = bokeh_model_page(table) - -# # click row 1 (which triggers the selection callback) -# cell = get_table_cell(page.driver, table, 1, 1) -# cell.click() -# results = page.results -# assert results['values'] == self.values - -# # now double click, enter the text 33 and -# enter_text_in_cell(page.driver, table, 1, 1, "0.5") - -# # click row 2 (which triggers callback again so we can inspect the data) -# cell = get_table_cell(page.driver, table, 2, 1) -# cell.click() -# results = page.results -# assert results['values'] == [0.5, 0.2] - -# assert page.has_no_console_errors() diff --git a/tests/integration/widgets/tables/test_copy_paste.py b/tests/integration/widgets/tables/test_copy_paste.py deleted file mode 100644 index 5c907b19492..00000000000 --- a/tests/integration/widgets/tables/test_copy_paste.py +++ /dev/null @@ -1,147 +0,0 @@ -#----------------------------------------------------------------------------- -# Copyright (c) Anaconda, Inc. All rights reserved. -# -# Powered by the Bokeh Development Team. -# -# The full license is in the file LICENSE.txt, distributed with this software. -#----------------------------------------------------------------------------- - -#----------------------------------------------------------------------------- -# Boilerplate -#----------------------------------------------------------------------------- -from __future__ import annotations # isort:skip - -import pytest ; pytest - -#----------------------------------------------------------------------------- -# Imports -#----------------------------------------------------------------------------- - -# Standard library imports -from time import sleep - -# External imports -from selenium.webdriver.common.keys import Keys - -# Bokeh imports -from bokeh.layouts import column -from bokeh.models import ( - ColumnDataSource, - CustomJS, - DataTable, - TableColumn, - TextInput, -) -from tests.support.plugins.project import BokehModelPage -from tests.support.util.selenium import ( - RECORD, - enter_text_in_element, - find_element_for, - get_table_row, - shift_click, -) - -#----------------------------------------------------------------------------- -# Tests -#----------------------------------------------------------------------------- - -pytest_plugins = ( - "tests.support.plugins.project", -) - - -@pytest.mark.selenium -class Test_DataTableCopyPaste: - def test_single_row_copy(self, bokeh_model_page: BokehModelPage) -> None: - data = {'x': [1,2,3,4], 'y': [1,1,1,1], 'd': ['foo', 'bar', 'baz', 'quux']} - source = ColumnDataSource(data) - table = DataTable(columns=[ - TableColumn(field="x", title="x"), - TableColumn(field="y", title="y"), - TableColumn(field="d", title="d"), - ], source=source) - - text_input = TextInput() - text_input.js_on_change('value', CustomJS(code=RECORD("value", "cb_obj.value"))) - - page = bokeh_model_page(column(table, text_input)) - - row = get_table_row(page.driver, table, 2) - row.click() - - enter_text_in_element(page.driver, row, Keys.INSERT, mod=Keys.CONTROL, click=0, enter=False) - - input_el = find_element_for(page.driver, text_input) - enter_text_in_element(page.driver, input_el, Keys.INSERT, mod=Keys.SHIFT, enter=False) - enter_text_in_element(page.driver, input_el, "") - - sleep(0.5) - results = page.results - - assert results['value'] == '1\t2\t1\tbar' - - assert page.has_no_console_errors() - - def test_single_row_copy_with_zero(self, bokeh_model_page: BokehModelPage) -> None: - data = {'x': [1,2,3,4], 'y': [0,0,0,0], 'd': ['foo', 'bar', 'baz', 'quux']} - source = ColumnDataSource(data) - table = DataTable(columns=[ - TableColumn(field="x", title="x"), - TableColumn(field="y", title="y"), - TableColumn(field="d", title="d"), - ], source=source) - - text_input = TextInput() - text_input.js_on_change('value', CustomJS(code=RECORD("value", "cb_obj.value"))) - - page = bokeh_model_page(column(table, text_input)) - - row = get_table_row(page.driver, table, 2) - row.click() - - enter_text_in_element(page.driver, row, Keys.INSERT, mod=Keys.CONTROL, click=0, enter=False) - - input_el = find_element_for(page.driver, text_input) - enter_text_in_element(page.driver, input_el, Keys.INSERT, mod=Keys.SHIFT, enter=False) - enter_text_in_element(page.driver, input_el, "") - - sleep(0.5) - results = page.results - - assert results['value'] == '1\t2\t0\tbar' - - assert page.has_no_console_errors() - - def test_multi_row_copy(self, bokeh_model_page: BokehModelPage) -> None: - data = {'x': [1,2,3,4], 'y': [0,1,2,3], 'd': ['foo', 'bar', 'baz', 'quux']} - source = ColumnDataSource(data) - table = DataTable(columns=[ - TableColumn(field="x", title="x"), - TableColumn(field="y", title="y"), - TableColumn(field="d", title="d"), - ], source=source) - - text_input = TextInput() - text_input.js_on_change('value', CustomJS(code=RECORD("value", "cb_obj.value"))) - - page = bokeh_model_page(column(table, text_input)) - - row = get_table_row(page.driver, table, 1) - row.click() - - row = get_table_row(page.driver, table, 3) - shift_click(page.driver, row) - - enter_text_in_element(page.driver, row, Keys.INSERT, mod=Keys.CONTROL, click=0, enter=False) - - input_el = find_element_for(page.driver, text_input) - enter_text_in_element(page.driver, input_el, Keys.INSERT, mod=Keys.SHIFT, enter=False) - enter_text_in_element(page.driver, input_el, "") - - results = page.results - - # XXX (bev) these should be newlines with a TextAreaInput but TextAreaInput - # is not working in tests for some reason presently - assert results['value'] == '0\t1\t0\tfoo 1\t2\t1\tbar 2\t3\t2\tbaz' - - assert page.has_no_console_errors() diff --git a/tests/integration/widgets/tables/test_data_table.py b/tests/integration/widgets/tables/test_data_table.py deleted file mode 100644 index 281eca11aef..00000000000 --- a/tests/integration/widgets/tables/test_data_table.py +++ /dev/null @@ -1,137 +0,0 @@ -#----------------------------------------------------------------------------- -# Copyright (c) Anaconda, Inc. All rights reserved. -# -# Powered by the Bokeh Development Team. -# -# The full license is in the file LICENSE.txt, distributed with this software. -#----------------------------------------------------------------------------- - -#----------------------------------------------------------------------------- -# Boilerplate -#----------------------------------------------------------------------------- -from __future__ import annotations # isort:skip - -import pytest ; pytest - -#----------------------------------------------------------------------------- -# Imports -#----------------------------------------------------------------------------- - -# Bokeh imports -from bokeh.layouts import column -from bokeh.models import ( - ColumnDataSource, - CustomJS, - DataTable, - TableColumn, -) -from tests.support.plugins.project import BokehModelPage -from tests.support.util.selenium import RECORD, ButtonWrapper, get_table_cell - -#----------------------------------------------------------------------------- -# Tests -#----------------------------------------------------------------------------- - -pytest_plugins = ( - "tests.support.plugins.project", -) - - -@pytest.mark.selenium -class Test_CellEditor_Base: - def setup_method(self): - source = ColumnDataSource({'values': self.values}) - column = TableColumn(field='values', title='values', editor=self.editor()) - self.table = DataTable(source=source, columns=[column], editable=True, width=600) - - # this is triggered on selection changes - source.selected.js_on_change('indices', CustomJS(args=dict(s=source), code=RECORD("values", "s.data.values"))) - - -@pytest.mark.selenium -class Test_DataTable: - def test_row_highlights_reflect_no_initial_selection(self, bokeh_model_page: BokehModelPage) -> None: - - source = ColumnDataSource({'values': [1, 2]}) - column = TableColumn(field='values', title='values') - table = DataTable(source=source, columns=[column], editable=False, width=600) - - page = bokeh_model_page(table) - - row0 = get_table_cell(page.driver, table, 1, 1) - assert 'selected' not in row0.get_attribute('class') - - row1 = get_table_cell(page.driver, table, 2, 1) - assert 'selected' not in row1.get_attribute('class') - - assert page.has_no_console_errors() - - def test_row_highlights_reflect_initial_selection(self, bokeh_model_page: BokehModelPage) -> None: - - source = ColumnDataSource({'values': [1, 2]}) - source.selected.indices = [1] - column = TableColumn(field='values', title='values') - table = DataTable(source=source, columns=[column], editable=False, width=600) - - page = bokeh_model_page(table) - - row0 = get_table_cell(page.driver, table, 1, 1) - assert 'selected' not in row0.get_attribute('class') - - row1 = get_table_cell(page.driver, table, 2, 1) - assert 'selected' in row1.get_attribute('class') - - assert page.has_no_console_errors() - - def test_row_highlights_reflect_ui_selection(self, bokeh_model_page: BokehModelPage) -> None: - - source = ColumnDataSource({'values': [1, 2]}) - column = TableColumn(field='values', title='values') - table = DataTable(source=source, columns=[column], editable=False, width=600) - - page = bokeh_model_page(table) - - row0 = get_table_cell(page.driver, table, 1, 1) - assert 'selected' not in row0.get_attribute('class') - - row1 = get_table_cell(page.driver, table, 2, 1) - assert 'selected' not in row1.get_attribute('class') - - cell = get_table_cell(page.driver, table, 2, 1) - cell.click() - - row0 = get_table_cell(page.driver, table, 1, 1) - assert 'selected' not in row0.get_attribute('class') - - row1 = get_table_cell(page.driver, table, 2, 1) - assert 'selected' in row1.get_attribute('class') - - assert page.has_no_console_errors() - - def test_row_highlights_reflect_js_selection(self, bokeh_model_page: BokehModelPage) -> None: - - source = ColumnDataSource({'values': [1, 2]}) - col = TableColumn(field='values', title='values') - table = DataTable(source=source, columns=[col], editable=False, width=600) - - button = ButtonWrapper("Click", callback=CustomJS(args=dict(s=source), code=""" - s.selected.indices = [1] - """)) - - page = bokeh_model_page(column(button.obj, table)) - - row0 = get_table_cell(page.driver, table, 1, 1) - assert 'selected' not in row0.get_attribute('class') - - row1 = get_table_cell(page.driver, table, 2, 1) - assert 'selected' not in row1.get_attribute('class') - - button.click(page.driver) - - row0 = get_table_cell(page.driver, table, 1, 1) - assert 'selected' not in row0.get_attribute('class') - - row1 = get_table_cell(page.driver, table, 2, 1) - assert 'selected' in row1.get_attribute('class') - - assert page.has_no_console_errors() diff --git a/tests/integration/widgets/tables/test_sortable.py b/tests/integration/widgets/tables/test_sortable.py deleted file mode 100644 index 03ca03ec70e..00000000000 --- a/tests/integration/widgets/tables/test_sortable.py +++ /dev/null @@ -1,146 +0,0 @@ -#----------------------------------------------------------------------------- -# Copyright (c) Anaconda, Inc. All rights reserved. -# -# Powered by the Bokeh Development Team. -# -# The full license is in the file LICENSE.txt, distributed with this software. -#----------------------------------------------------------------------------- - -#----------------------------------------------------------------------------- -# Boilerplate -#----------------------------------------------------------------------------- -from __future__ import annotations # isort:skip - -import pytest ; pytest - -#----------------------------------------------------------------------------- -# Imports -#----------------------------------------------------------------------------- - -# Bokeh imports -from bokeh.models import ColumnDataSource, DataTable, TableColumn -from tests.support.plugins.project import BokehModelPage -from tests.support.util.selenium import get_table_cell, get_table_header - -#----------------------------------------------------------------------------- -# Tests -#----------------------------------------------------------------------------- - -pytest_plugins = ( - "tests.support.plugins.project", -) - - -@pytest.mark.selenium -class Test_DataTableSortable: - def test_columns_sortable(self, bokeh_model_page: BokehModelPage) -> None: - data = {'x': [1,2,3,4], 'y': [4, 3, 2, 1], 'd': ['foo', 'bar', 'baz', 'quux']} - source = ColumnDataSource(data) - table = DataTable(columns=[ - TableColumn(field="x", title="x"), - TableColumn(field="y", title="y", sortable=False), - TableColumn(field="d", title="d", sortable=True), - ], source=source) - - page = bokeh_model_page(table) - - # index column - h1 = get_table_header(page.driver, table, 1) - assert "slick-header-sortable" in h1.get_attribute('class') - - h2 = get_table_header(page.driver, table, 2) - assert "slick-header-sortable" in h2.get_attribute('class') - - h3 = get_table_header(page.driver, table, 3) - assert "slick-header-sortable" not in h3.get_attribute('class') - - h4 = get_table_header(page.driver, table, 4) - assert "slick-header-sortable" in h4.get_attribute('class') - - assert page.has_no_console_errors() - - def test_click_nonsortable(self, bokeh_model_page: BokehModelPage) -> None: - data = {'x': [1,2,3,4], 'y': [4, 3, 2, 1], 'd': ['foo', 'bar', 'baz', 'quux']} - source = ColumnDataSource(data) - table = DataTable(columns=[ - TableColumn(field="x", title="x"), - TableColumn(field="y", title="y", sortable=False), - TableColumn(field="d", title="d", sortable=True), - ], source=source) - - page = bokeh_model_page(table) - - for i, x in enumerate(['foo', 'bar', 'baz', 'quux'], 1): - elt = get_table_cell(page.driver, table, i, 3) - assert elt.text == x - - h3 = get_table_header(page.driver, table, 3) - h3.click() - - for i, x in enumerate(['foo', 'bar', 'baz', 'quux'], 1): - elt = get_table_cell(page.driver, table, i, 3) - assert elt.text == x - - assert page.has_no_console_errors() - - def test_click_sortable(self, bokeh_model_page: BokehModelPage) -> None: - data = {'x': [1,2,3,4], 'y': [4, 3, 2, 1], 'd': ['foo', 'bar', 'baz', 'quux']} - source = ColumnDataSource(data) - table = DataTable(columns=[ - TableColumn(field="x", title="x"), - TableColumn(field="y", title="y", sortable=False), - TableColumn(field="d", title="d", sortable=True), - ], source=source) - - page = bokeh_model_page(table) - - for i, x in enumerate(['foo', 'bar', 'baz', 'quux'], 1): - elt = get_table_cell(page.driver, table, i, 3) - assert elt.text == x - - h4 = get_table_header(page.driver, table, 4) - h4.click() - - for i, x in enumerate(['bar', 'baz', 'foo', 'quux'], 1): - elt = get_table_cell(page.driver, table, i, 3) - assert elt.text == x - - h4 = get_table_header(page.driver, table, 4) - h4.click() - - for i, x in enumerate(['quux', 'foo', 'baz', 'bar'], 1): - elt = get_table_cell(page.driver, table, i, 3) - assert elt.text == x - - assert page.has_no_console_errors() - - def test_table_unsortable(self, bokeh_model_page: BokehModelPage) -> None: - data = {'x': [1,2,3,4], 'y': [4, 3, 2, 1], 'd': ['foo', 'bar', 'baz', 'quux']} - source = ColumnDataSource(data) - table = DataTable(columns=[ - TableColumn(field="x", title="x"), - TableColumn(field="y", title="y", sortable=False), - TableColumn(field="d", title="d", sortable=True), - ], sortable=False, source=source) - - page = bokeh_model_page(table) - - for i, x in enumerate(['foo', 'bar', 'baz', 'quux'], 1): - elt = get_table_cell(page.driver, table, i, 3) - assert elt.text == x - - h4 = get_table_header(page.driver, table, 4) - h4.click() - - for i, x in enumerate(['foo', 'bar', 'baz', 'quux'], 1): - elt = get_table_cell(page.driver, table, i, 3) - assert elt.text == x - - h4 = get_table_header(page.driver, table, 4) - h4.click() - - for i, x in enumerate(['foo', 'bar', 'baz', 'quux'], 1): - elt = get_table_cell(page.driver, table, i, 3) - assert elt.text == x - - assert page.has_no_console_errors() diff --git a/tests/integration/widgets/tables/test_source_updates.py b/tests/integration/widgets/tables/test_source_updates.py deleted file mode 100644 index 8fc58425554..00000000000 --- a/tests/integration/widgets/tables/test_source_updates.py +++ /dev/null @@ -1,685 +0,0 @@ -#----------------------------------------------------------------------------- -# Copyright (c) Anaconda, Inc. All rights reserved. -# -# Powered by the Bokeh Development Team. -# -# The full license is in the file LICENSE.txt, distributed with this software. -#----------------------------------------------------------------------------- - -#----------------------------------------------------------------------------- -# Boilerplate -#----------------------------------------------------------------------------- -from __future__ import annotations # isort:skip - -import pytest ; pytest - -#----------------------------------------------------------------------------- -# Imports -#----------------------------------------------------------------------------- - -# Bokeh imports -from bokeh.layouts import column -from bokeh.models import ( - Button, - ColumnDataSource, - CustomJS, - DataTable, - NumberEditor, - Plot, - Range1d, - Rect, - TableColumn, - TapTool, -) -from tests.support.plugins.project import BokehServerPage, SinglePlotPage -from tests.support.util.selenium import ( - RECORD, - alt_click, - enter_text_in_cell, - find_element_for, - get_table_cell, - get_table_column_cells, - get_table_row, - get_table_selected_rows, - shift_click, - sort_table_column, -) - -#----------------------------------------------------------------------------- -# Tests -#----------------------------------------------------------------------------- - -pytest_plugins = ( - "tests.support.plugins.project", -) - -def _is_cds_data_patch(evt): - return evt['kind'] == 'ModelChanged' and evt['attr'] == 'data' - -def has_cds_data_patches(msgs): - for msg in msgs: - if msg.msgtype == "PATCH-DOC": - if any(_is_cds_data_patch(evt) for evt in msg.content.get('events', [])): - return True - - return False - - -@pytest.mark.selenium -class Test_DataTableSource: - def test_server_source_patch_does_not_duplicate_data_update_event(self, bokeh_server_page: BokehServerPage) -> None: - btn = Button(label="Click Me!") - - data = {'x': [1,2,3,4], 'y': [10,20,30,40]} - source = ColumnDataSource(data) - - table = DataTable(columns=[ - TableColumn(field="x"), - TableColumn(field="y"), - ], source=source, editable=False) - - def modify_doc(doc): - plot = Plot(height=400, width=400, x_range=Range1d(0, 1), y_range=Range1d(0, 1), min_border=0) - plot.tags.append(CustomJS(name="custom-action", args=dict(s=source), code=RECORD("data", "s.data"))) - - def btn_click(event): - source.patch({"x": [(0, 42)]}) - btn.on_event('button_click', btn_click) - - doc.add_root(column(plot, table, btn)) - - page = bokeh_server_page(modify_doc) - - page.eval_custom_action() - - results = page.results - assert results == {'data': {'x': [1,2,3,4], 'y': [10,20,30,40]}} - - btn_el = find_element_for(page.driver, btn) - btn_el.click() - - page.eval_custom_action() - - results = page.results - assert results == {'data': {'x': [42,2,3,4], 'y': [10,20,30,40]}} - - # if the server receives something back like: - # - # Message 'PATCH-DOC' (revision 1) content: { - # 'events': [{ - # 'kind': 'ModelChanged', - # 'model': {'id': '1001'}, - # 'attr': 'data', 'new': {'x': [42, 2, 3, 4], 'y': [10, 20, 30, 40]} - # }], - # 'references': [] - # } - # - # Then that means the client got our patch message and erroneously ping - # ponged a full data update back to us - assert not has_cds_data_patches(page.message_test_port.received) - - assert page.has_no_console_errors() - - def test_server_source_stream_does_not_duplicate_data_update_event(self, bokeh_server_page: BokehServerPage) -> None: - btn = Button(label="Click Me!") - - data = {'x': [1,2,3,4], 'y': [10,20,30,40]} - source = ColumnDataSource(data) - - table = DataTable(columns=[ - TableColumn(field="x"), - TableColumn(field="y"), - ], source=source, editable=False) - - def modify_doc(doc): - plot = Plot(height=400, width=400, x_range=Range1d(0, 1), y_range=Range1d(0, 1), min_border=0) - plot.tags.append(CustomJS(name="custom-action", args=dict(s=source), code=RECORD("data", "s.data"))) - - def btn_click(event): - source.stream({"x": [5], "y": [50]}) - btn.on_event('button_click', btn_click) - - doc.add_root(column(plot, table, btn)) - - page = bokeh_server_page(modify_doc) - - page.eval_custom_action() - - results = page.results - assert results == {'data': {'x': [1,2,3,4], 'y': [10,20,30,40]}} - - btn_el = find_element_for(page.driver, btn) - btn_el.click() - - page.eval_custom_action() - - results = page.results - assert results == {'data': {'x': [1,2,3,4,5], 'y': [10,20,30,40,50]}} - - # if the server receives something back like: - # - # Message 'PATCH-DOC' (revision 1) content: { - # 'events': [{ - # 'kind': 'ModelChanged', - # 'model': {'id': '1001'}, - # 'attr': 'data', 'new': {'x': [1, 2, 3, 4, 5], 'y': [10, 20, 30, 40, 50]} - # }], - # 'references': [] - # } - # - # Then that means the client got our stream message and erroneously ping - # ponged a full data update back to us - assert not has_cds_data_patches(page.message_test_port.received) - - assert page.has_no_console_errors() - - def test_server_source_update_does_not_duplicate_data_update_event(self, bokeh_server_page: BokehServerPage) -> None: - btn = Button(label="Click Me!") - - data = {'x': [1,2,3,4], 'y': [10,20,30,40]} - source = ColumnDataSource(data) - - table = DataTable(columns=[ - TableColumn(field="x"), - TableColumn(field="y"), - ], source=source, editable=False) - - def modify_doc(doc): - - plot = Plot(height=400, width=400, x_range=Range1d(0, 1), y_range=Range1d(0, 1), min_border=0) - plot.tags.append(CustomJS(name="custom-action", args=dict(s=source), code=RECORD("data", "s.data"))) - - def btn_click(event): - source.data = {'x': [5,6,7,8], 'y': [50,60,70,80]} - btn.on_event('button_click', btn_click) - - doc.add_root(column(plot, table, btn)) - - page = bokeh_server_page(modify_doc) - - page.eval_custom_action() - - results = page.results - assert results == {'data': {'x': [1,2,3,4], 'y': [10,20,30,40]}} - - btn_el = find_element_for(page.driver, btn) - btn_el.click() - - page.eval_custom_action() - - results = page.results - assert results == {'data': {'x': [5,6,7,8], 'y': [50,60,70,80]}} - - # if the server receives something back like: - # - # Message 'PATCH-DOC' (revision 1) content: { - # 'events': [{ - # 'kind': 'ModelChanged', - # 'model': {'id': '1001'}, - # 'attr': 'data', 'new': {'x': [1, 2, 3, 4, 5], 'y': [10, 20, 30, 40, 50]} - # }], - # 'references': [] - # } - # - # Then that means the client got our stream message and erroneously ping - # ponged a full data update back to us - assert not has_cds_data_patches(page.message_test_port.received) - - assert page.has_no_console_errors() - - def test_server_edit_does_not_duplicate_data_update_event(self, bokeh_server_page: BokehServerPage) -> None: - data = {'x': [1,2,3,4], 'y': [10,20,30,40]} - source = ColumnDataSource(data) - - table = DataTable(columns=[ - TableColumn(field="x"), - TableColumn(field="y", editor=NumberEditor()), - ], source=source, editable=True) - - def modify_doc(doc): - plot = Plot(height=400, width=400, x_range=Range1d(0, 1), y_range=Range1d(0, 1), min_border=0) - plot.tags.append(CustomJS(name="custom-action", args=dict(s=source), code=RECORD("data", "s.data"))) - - doc.add_root(column(plot, table)) - - page = bokeh_server_page(modify_doc) - - page.eval_custom_action() - - results = page.results - assert results == {'data': {'x': [1,2,3,4], 'y': [10,20,30,40]}} - - cell = get_table_cell(page.driver, table, 3, 2) - assert cell.text == '30' - enter_text_in_cell(page.driver, table, 3, 2, '100') - - page.eval_custom_action() - - results = page.results - assert results == {'data': {'x': [1,2,3,4], 'y': [10, 20, 100, 40]}} - - # if the server receives something back like: - # - # Message 'PATCH-DOC' (revision 1) content: { - # 'events': [{ - # 'kind': 'ModelChanged', - # 'model': {'id': '1001'}, - # 'attr': 'data', 'new': {'x': [1,2,3,4], 'y': [10, 20, 100, 40]} - # }], - # 'references': [] - # } - # - # Then that means the client got our stream message and erroneously ping - # ponged a full data update back to us - assert not has_cds_data_patches(page.message_test_port.received) - - assert page.has_no_console_errors() - - def test_server_basic_selection(self, bokeh_server_page: BokehServerPage) -> None: - data = {'x': [1,2,3,4,5,6], 'y': [60,50,40,30,20,10]} - source = ColumnDataSource(data) - - table = DataTable(columns=[ - TableColumn(field="x"), - TableColumn(field="y"), - ], source=source, editable=False) - - def modify_doc(doc): - plot = Plot(height=400, width=400, x_range=Range1d(0, 1), y_range=Range1d(0, 1), min_border=0) - plot.tags.append(CustomJS(name="custom-action", args=dict(s=source), code=RECORD("indices", "s.selected.indices"))) - - doc.add_root(column(plot, table)) - - page = bokeh_server_page(modify_doc) - - page.eval_custom_action() - - results = page.results - assert results == {'indices': []} - assert set(source.selected.indices) == set() - assert get_table_selected_rows(page.driver, table) == set() - - # select the third row - row = get_table_row(page.driver, table, 3) - row.click() - - page.eval_custom_action() - - results = page.results - assert results == {'indices': [2]} - assert source.selected.indices == [2] - assert get_table_selected_rows(page.driver, table) == {2} - - # select the first row - row = get_table_row(page.driver, table, 1) - row.click() - - page.eval_custom_action() - - results = page.results - assert results == {'indices': [0]} - assert source.selected.indices == [0] - assert get_table_selected_rows(page.driver, table) == {0} - - assert page.has_no_console_errors() - - def test_server_basic_mulitselection(self, bokeh_server_page: BokehServerPage) -> None: - data = {'x': [1,2,3,4,5,6], 'y': [60,50,40,30,20,10]} - source = ColumnDataSource(data) - - table = DataTable(columns=[ - TableColumn(field="x"), - TableColumn(field="y"), - ], source=source, editable=False) - - def modify_doc(doc): - plot = Plot(height=400, width=400, x_range=Range1d(0, 1), y_range=Range1d(0, 1), min_border=0) - plot.tags.append(CustomJS(name="custom-action", args=dict(s=source), code=RECORD("indices", "s.selected.indices"))) - - doc.add_root(column(plot, table)) - - page = bokeh_server_page(modify_doc) - - page.eval_custom_action() - - results = page.results - assert results == {'indices': []} - assert set(source.selected.indices) == set() - assert get_table_selected_rows(page.driver, table) == set() - - # select the third row - row = get_table_row(page.driver, table, 2) - row.click() - - row = get_table_row(page.driver, table, 4) - shift_click(page.driver, row) - - page.eval_custom_action() - - results = page.results - assert set(results["indices"]) == {1, 2, 3} - assert set(source.selected.indices) == {1, 2, 3} - assert get_table_selected_rows(page.driver, table) == {1, 2, 3} - - row = get_table_row(page.driver, table, 6) - alt_click(page.driver, row) - - page.eval_custom_action() - - results = page.results - assert set(results["indices"]) == {1, 2, 3, 5} - assert set(source.selected.indices) == {1, 2, 3, 5} - assert get_table_selected_rows(page.driver, table) == {1, 2, 3, 5} - - assert page.has_no_console_errors() - - def test_server_sorted_after_data_update(self, bokeh_server_page: BokehServerPage) -> None: - button = Button() - - data = {'x': [1,2,5,6], 'y': [60,50,20,10]} - source = ColumnDataSource(data) - - table = DataTable(columns=[ - TableColumn(field="x", title="x", sortable=True), - TableColumn(field="y", title="y", sortable=True), - ], source=source, editable=False) - - def modify_doc(doc): - plot = Plot(height=400, width=400, x_range=Range1d(0, 1), y_range=Range1d(0, 1), min_border=0) - plot.tags.append(CustomJS(name="custom-action", args=dict(s=source), code=RECORD("data", "s.data"))) - - def cb(): - source.data = {'x': [0,1,2,3,4,5,6,7], 'y': [70,60,50,40,30,20,10,0]} - button.on_event('button_click', cb) - - doc.add_root(column(plot, table, button)) - - page = bokeh_server_page(modify_doc) - - page.eval_custom_action() - - results = page.results - assert results == {'data': {'x': [1,2,5,6], 'y': [60,50,20,10]}} - - assert get_table_column_cells(page.driver, table, 1) == ['1', '2', '5', '6'] - assert get_table_column_cells(page.driver, table, 2) == ['60', '50', '20', '10'] - - sort_table_column(page.driver, table, 1) - - assert get_table_column_cells(page.driver, table, 1) == ['1', '2', '5', '6'] - assert get_table_column_cells(page.driver, table, 2) == ['60', '50', '20', '10'] - - sort_table_column(page.driver, table, 2, True) - - assert get_table_column_cells(page.driver, table, 1) == ['6', '5', '2', '1'] - assert get_table_column_cells(page.driver, table, 2) == ['10', '20', '50', '60'] - - button_el = find_element_for(page.driver, button) - button_el.click() - - page.eval_custom_action() - - results = page.results - assert results == {'data': {'x': [0,1,2,3,4,5,6,7], 'y': [70,60,50,40,30,20,10,0]}} - assert source.data == {'x': [0,1,2,3,4,5,6,7], 'y': [70,60,50,40,30,20,10,0]} - - assert get_table_column_cells(page.driver, table, 1) == ['7', '6', '5', '4', '3', '2', '1', '0'] - assert get_table_column_cells(page.driver, table, 2) == ['0', '10', '20', '30', '40', '50', '60', '70'] - - assert page.has_no_console_errors() - - def test_server_sorted_after_patch(self, bokeh_server_page: BokehServerPage) -> None: - button = Button() - - data = {'x': [1,2,5,6], 'y': [60,50,20,10]} - source = ColumnDataSource(data) - - table = DataTable(columns=[ - TableColumn(field="x", title="x", sortable=True), - TableColumn(field="y", title="y", sortable=True), - ], source=source, editable=False) - - def modify_doc(doc): - plot = Plot(height=400, width=400, x_range=Range1d(0, 1), y_range=Range1d(0, 1), min_border=0) - plot.tags.append(CustomJS(name="custom-action", args=dict(s=source), code=RECORD("data", "s.data"))) - - def cb(): - source.patch({'y': [[2, 100]]}) - button.on_event('button_click', cb) - - doc.add_root(column(plot, table, button)) - - page = bokeh_server_page(modify_doc) - - page.eval_custom_action() - - results = page.results - assert results == {'data': {'x': [1,2,5,6], 'y': [60,50,20,10]}} - - assert get_table_column_cells(page.driver, table, 1) == ['1', '2', '5', '6'] - assert get_table_column_cells(page.driver, table, 2) == ['60', '50', '20', '10'] - - sort_table_column(page.driver, table, 1) - - assert get_table_column_cells(page.driver, table, 1) == ['1', '2', '5', '6'] - assert get_table_column_cells(page.driver, table, 2) == ['60', '50', '20', '10'] - - sort_table_column(page.driver, table, 2, True) - - assert get_table_column_cells(page.driver, table, 1) == ['6', '5', '2', '1'] - assert get_table_column_cells(page.driver, table, 2) == ['10', '20', '50', '60'] - - button_el = find_element_for(page.driver, button) - button_el.click() - - page.eval_custom_action() - - results = page.results - assert results == {'data': {'x': [1,2,5,6], 'y': [60,50,100,10]}} - assert source.data == {'x': [1,2,5,6], 'y': [60,50,100,10]} - - assert get_table_column_cells(page.driver, table, 1) == ['6', '2', '1', '5'] - assert get_table_column_cells(page.driver, table, 2) == ['10', '50', '60', '100'] - - assert page.has_no_console_errors() - - def test_server_sorted_after_stream(self, bokeh_server_page: BokehServerPage) -> None: - button = Button() - - data = {'x': [1,2,5,6], 'y': [60,50,20,10]} - source = ColumnDataSource(data) - - table = DataTable(columns=[ - TableColumn(field="x", title="x", sortable=True), - TableColumn(field="y", title="y", sortable=True), - ], source=source, editable=False) - - def modify_doc(doc): - plot = Plot(height=400, width=400, x_range=Range1d(0, 1), y_range=Range1d(0, 1), min_border=0) - plot.tags.append(CustomJS(name="custom-action", args=dict(s=source), code=RECORD("data", "s.data"))) - - def cb(): - source.stream({'x': [100], 'y': [100]}) - button.on_event('button_click', cb) - - doc.add_root(column(plot, table, button)) - - page = bokeh_server_page(modify_doc) - - page.eval_custom_action() - - results = page.results - assert results == {'data': {'x': [1,2,5,6], 'y': [60,50,20,10]}} - - assert get_table_column_cells(page.driver, table, 1) == ['1', '2', '5', '6'] - assert get_table_column_cells(page.driver, table, 2) == ['60', '50', '20', '10'] - - sort_table_column(page.driver, table, 1) - - assert get_table_column_cells(page.driver, table, 1) == ['1', '2', '5', '6'] - assert get_table_column_cells(page.driver, table, 2) == ['60', '50', '20', '10'] - - sort_table_column(page.driver, table, 2, True) - - assert get_table_column_cells(page.driver, table, 1) == ['6', '5', '2', '1'] - assert get_table_column_cells(page.driver, table, 2) == ['10', '20', '50', '60'] - - button_el = find_element_for(page.driver, button) - button_el.click() - - page.eval_custom_action() - - results = page.results - assert results == {'data': {'x': [1,2,5,6,100], 'y': [60,50,20,10,100]}} - assert source.data == {'x': [1,2,5,6,100], 'y': [60,50,20,10,100]} - - assert get_table_column_cells(page.driver, table, 1) == ['6', '5', '2', '1', '100'] - assert get_table_column_cells(page.driver, table, 2) == ['10', '20', '50', '60', '100'] - - assert page.has_no_console_errors() - - def test_server_sorted_after_edit(self, bokeh_server_page: BokehServerPage) -> None: - data = {'x': [1,2,5,6], 'y': [60,50,20,10]} - source = ColumnDataSource(data) - - table = DataTable(columns=[ - TableColumn(field="x", title="x", sortable=True), - TableColumn(field="y", title="y", sortable=True, editor=NumberEditor()), - ], source=source, editable=True) - - def modify_doc(doc): - plot = Plot(height=400, width=400, x_range=Range1d(0, 1), y_range=Range1d(0, 1), min_border=0) - plot.tags.append(CustomJS(name="custom-action", args=dict(s=source), code=RECORD("data", "s.data"))) - - doc.add_root(column(plot, table)) - - page = bokeh_server_page(modify_doc) - - page.eval_custom_action() - - results = page.results - assert results == {'data': {'x': [1,2,5,6], 'y': [60,50,20,10]}} - - assert get_table_column_cells(page.driver, table, 1) == ['1', '2', '5', '6'] - assert get_table_column_cells(page.driver, table, 2) == ['60', '50', '20', '10'] - - sort_table_column(page.driver, table, 1) - - assert get_table_column_cells(page.driver, table, 1) == ['1', '2', '5', '6'] - assert get_table_column_cells(page.driver, table, 2) == ['60', '50', '20', '10'] - - sort_table_column(page.driver, table, 2, True) - - assert get_table_column_cells(page.driver, table, 1) == ['6', '5', '2', '1'] - assert get_table_column_cells(page.driver, table, 2) == ['10', '20', '50', '60'] - - cell = get_table_cell(page.driver, table, 3, 2) - assert cell.text == '50' - enter_text_in_cell(page.driver, table, 3, 2, '100') - - page.eval_custom_action() - - results = page.results - assert results == {'data': {'x': [1,2,5,6], 'y': [60,100,20,10]}} - assert source.data == {'x': [1,2,5,6], 'y': [60,100,20,10]} - - assert get_table_column_cells(page.driver, table, 1) == ['6', '5', '1', '2'] - assert get_table_column_cells(page.driver, table, 2) == ['10', '20', '60', '100'] - - assert page.has_no_console_errors() - - def test_server_source_updated_after_edit(self, bokeh_server_page: BokehServerPage) -> None: - data = {'x': [1,2,5,6], 'y': [60,50,20,10]} - source = ColumnDataSource(data) - - table = DataTable(columns=[ - TableColumn(field="x", title="x", sortable=True), - TableColumn(field="y", title="y", sortable=True, editor=NumberEditor()), - ], source=source, editable=True) - - def modify_doc(doc): - plot = Plot(height=400, width=400, x_range=Range1d(0, 1), y_range=Range1d(0, 1), min_border=0) - plot.tags.append(CustomJS(name="custom-action", args=dict(s=source), code=RECORD("data", "s.data"))) - - doc.add_root(column(plot, table)) - - page = bokeh_server_page(modify_doc) - - page.eval_custom_action() - - results = page.results - assert results == {'data': {'x': [1,2,5,6], 'y': [60,50,20,10]}} - - assert get_table_column_cells(page.driver, table, 1) == ['1', '2', '5', '6'] - assert get_table_column_cells(page.driver, table, 2) == ['60', '50', '20', '10'] - - cell = get_table_cell(page.driver, table, 3, 2) - assert cell.text == '20' - enter_text_in_cell(page.driver, table, 3, 2, '100') - - page.eval_custom_action() - - results = page.results - assert results == {'data': {'x': [1,2,5,6], 'y': [60,50,100,10]}} - assert source.data == {'x': [1,2,5,6], 'y': [60,50,100,10]} - - assert get_table_column_cells(page.driver, table, 1) == ['1', '2', '5', '6'] - assert get_table_column_cells(page.driver, table, 2) == ['60', '50', '100', '10'] - - assert page.has_no_console_errors() - - def test_server_source_callback_triggered_after_edit(self, bokeh_server_page: BokehServerPage) -> None: - data = {'x': [1,2,5,6], 'y': [60,50,20,10]} - source = ColumnDataSource(data) - - table = DataTable(columns=[ - TableColumn(field="x", title="x", sortable=True), - TableColumn(field="y", title="y", sortable=True, editor=NumberEditor()), - ], source=source, editable=True) - - result = [] - - def modify_doc(doc): - plot = Plot(height=400, width=400, x_range=Range1d(0, 1), y_range=Range1d(0, 1), min_border=0) - - def cb(attr, old, new): - result.append("CALLED") - - source.on_change('data', cb) - doc.add_root(column(plot, table)) - - page = bokeh_server_page(modify_doc) - - assert result == [] - - cell = get_table_cell(page.driver, table, 3, 2) - assert cell.text == '20' - enter_text_in_cell(page.driver, table, 3, 2, '100') - - assert result == ["CALLED"] - - assert page.has_no_console_errors() - - def test_glyph_selection_updates_table(self, single_plot_page: SinglePlotPage) -> None: - plot = Plot(height=800, width=1000) - - data = {'x': [1,2,3,4], 'y': [1, 1, 1, 1]} - source = ColumnDataSource(data) - table = DataTable(columns=[ - TableColumn(field="x", title="x", sortable=True), - TableColumn(field="y", title="y", sortable=True, editor=NumberEditor()), - ], source=source, editable=True) - - plot.add_glyph(source, Rect(x='x', y='y', width=1.5, height=1)) - plot.add_tools(TapTool(callback=CustomJS(code=RECORD("indices", "cb_data.source.selected.indices")))) - - page = single_plot_page(column(plot, table)) - - page.click_canvas_at_position(plot, 500, 400) - assert set(page.results["indices"]) == {1, 2} - - assert get_table_selected_rows(page.driver, table) == {1, 2} - - assert page.has_no_console_errors() - - assert page.has_no_console_errors() diff --git a/tests/integration/widgets/test_autocomplete_input.py b/tests/integration/widgets/test_autocomplete_input.py deleted file mode 100644 index 0b18020ebda..00000000000 --- a/tests/integration/widgets/test_autocomplete_input.py +++ /dev/null @@ -1,518 +0,0 @@ -#----------------------------------------------------------------------------- -# Copyright (c) Anaconda, Inc. All rights reserved. -# -# Powered by the Bokeh Development Team. -# -# The full license is in the file LICENSE.txt, distributed with this software. -#----------------------------------------------------------------------------- - -#----------------------------------------------------------------------------- -# Boilerplate -#----------------------------------------------------------------------------- -from __future__ import annotations # isort:skip - -import pytest ; pytest - -#----------------------------------------------------------------------------- -# Imports -#----------------------------------------------------------------------------- - -# External imports -from selenium.webdriver.common.by import By -from selenium.webdriver.common.keys import Keys - -# Bokeh imports -from bokeh.application.handlers.function import ModifyDoc -from bokeh.layouts import column -from bokeh.models import ( - AutocompleteInput, - ColumnDataSource, - CustomJS, - Plot, - Range1d, - Scatter, -) -from tests.support.plugins.project import BokehModelPage, BokehServerPage -from tests.support.util.selenium import ( - RECORD, - enter_text_in_element, - find_element_for, - hover_element, -) - -#----------------------------------------------------------------------------- -# Tests -#----------------------------------------------------------------------------- - -pytest_plugins = ( - "tests.support.plugins.project", -) - -def mk_modify_doc(input_box: AutocompleteInput) -> tuple[ModifyDoc, Plot]: - plot = Plot(height=400, width=400, x_range=Range1d(0, 1), y_range=Range1d(0, 1), min_border=0) - def modify_doc(doc): - source = ColumnDataSource(dict(x=[1, 2], y=[1, 1], val=["a", "b"])) - plot.add_glyph(source, Scatter(x='x', y='y', size=20)) - plot.tags.append(CustomJS(name="custom-action", args=dict(s=source), code=RECORD("data", "s.data"))) - input_box.title = "title" - input_box.value = "400" - input_box.completions = ["100001", "12344556", "12344557", "3194567289", "209374209374"] - def cb(attr, old, new): - source.data['val'] = [old, new] - input_box.on_change('value', cb) - doc.add_root(column(input_box, plot)) - return modify_doc, plot - -@pytest.mark.selenium -class Test_AutocompleteInput: - def test_displays_text_input(self, bokeh_model_page: BokehModelPage) -> None: - text_input = AutocompleteInput(completions = ["100001", "12344556", "12344557", "3194567289", "209374209374"]) - - page = bokeh_model_page(text_input) - - el = find_element_for(page.driver, text_input, "input") - assert el.get_attribute('type') == "text" - - assert page.has_no_console_errors() - - def test_displays_title(self, bokeh_model_page: BokehModelPage) -> None: - text_input = AutocompleteInput(title="title", completions = ["100001", "12344556", "12344557", "3194567289", "209374209374"]) - - page = bokeh_model_page(text_input) - - el = find_element_for(page.driver, text_input, "label") - assert el.text == "title" - el = find_element_for(page.driver, text_input, "input") - assert el.get_attribute('placeholder') == "" - assert el.get_attribute('type') == "text" - - assert page.has_no_console_errors() - - def test_displays_menu(self, bokeh_model_page: BokehModelPage) -> None: - text_input = AutocompleteInput(title="title", completions = ["100001", "12344556", "12344557", "3194567289", "209374209374"]) - - page = bokeh_model_page(text_input) - - el = find_element_for(page.driver, text_input, ".bk-menu") - assert 'display: none;' in el.get_attribute('style') - - # double click to highlight and overwrite old text - el = find_element_for(page.driver, text_input, "input") - enter_text_in_element(page.driver, el, "100", click=2, enter=False) - - el = find_element_for(page.driver, text_input, ".bk-menu") - assert 'display: none;' not in el.get_attribute('style') - - items = el.find_elements(By.TAG_NAME, "div") - assert len(items) == 1 - assert items[0].text == "100001" - assert "bk-active" in items[0].get_attribute('class') - - el = find_element_for(page.driver, text_input, "input") - enter_text_in_element(page.driver, el, "123", click=2, enter=False) - - el = find_element_for(page.driver, text_input, ".bk-menu") - assert 'display: none;' not in el.get_attribute('style') - - items = el.find_elements(By.TAG_NAME, "div") - assert len(items) == 2 - assert items[0].text == "12344556" - assert items[1].text == "12344557" - assert "bk-active" in items[0].get_attribute('class') - assert "bk-active" not in items[1].get_attribute('class') - - enter_text_in_element(page.driver, el, Keys.DOWN, click=0, enter=False) - items = el.find_elements(By.TAG_NAME, "div") - assert len(items) == 2 - assert items[0].text == "12344556" - assert items[1].text == "12344557" - assert "bk-active" not in items[0].get_attribute('class') - assert "bk-active" in items[1].get_attribute('class') - - assert page.has_no_console_errors() - - def test_min_characters(self, bokeh_model_page: BokehModelPage) -> None: - text_input = AutocompleteInput(title="title", - completions = ["100001", "12344556", "12344557", "3194567289", "209374209374", "aaaaaa", "aaabbb", "AAAaAA", "AAABbB"], - min_characters=1) - - page = bokeh_model_page(text_input) - - el = find_element_for(page.driver, text_input, ".bk-menu") - assert 'display: none;' in el.get_attribute('style') - - # double click to highlight and overwrite old text - el = find_element_for(page.driver, text_input, "input") - enter_text_in_element(page.driver, el, "1", click=2, enter=False) - - el = find_element_for(page.driver, text_input, ".bk-menu") - assert 'display: none;' not in el.get_attribute('style') - - items = el.find_elements(By.TAG_NAME, "div") - assert len(items) == 3 - assert items[0].text == "100001" - assert items[1].text == "12344556" - assert items[2].text == "12344557" - assert "bk-active" in items[0].get_attribute('class') - assert "bk-active" not in items[1].get_attribute('class') - assert "bk-active" not in items[2].get_attribute('class') - - def test_case_insensitivity(self, bokeh_model_page: BokehModelPage) -> None: - text_input = AutocompleteInput(title="title", case_sensitive=False, completions = ["100001", "aaaaaa", "aaabbb", "AAAaAA", "AAABbB"]) - - page = bokeh_model_page(text_input) - - el = find_element_for(page.driver, text_input, ".bk-menu") - assert 'display: none;' in el.get_attribute('style') - - # double click to highlight and overwrite old text - el = find_element_for(page.driver, text_input, "input") - enter_text_in_element(page.driver, el, "aAa", click=2, enter=False) - - el = find_element_for(page.driver, text_input, ".bk-menu") - assert 'display: none;' not in el.get_attribute('style') - - items = el.find_elements(By.TAG_NAME, "div") - assert len(items) == 4 - assert items[0].text == "aaaaaa" - assert items[1].text == "aaabbb" - assert items[2].text == "AAAaAA" - assert items[3].text == "AAABbB" - assert "bk-active" in items[0].get_attribute('class') - - el = find_element_for(page.driver, text_input, "input") - enter_text_in_element(page.driver, el, "aAaB", click=2, enter=False) - - el = find_element_for(page.driver, text_input, ".bk-menu") - assert 'display: none;' not in el.get_attribute('style') - - items = el.find_elements(By.TAG_NAME, "div") - assert len(items) == 2 - assert items[0].text == "aaabbb" - assert items[1].text == "AAABbB" - assert "bk-active" in items[0].get_attribute('class') - assert "bk-active" not in items[1].get_attribute('class') - - enter_text_in_element(page.driver, el, Keys.DOWN, click=0, enter=False) - items = el.find_elements(By.TAG_NAME, "div") - assert len(items) == 2 - assert items[0].text == "aaabbb" - assert items[1].text == "AAABbB" - assert "bk-active" not in items[0].get_attribute('class') - assert "bk-active" in items[1].get_attribute('class') - - assert page.has_no_console_errors() - - def test_case_sensitivity(self, bokeh_model_page: BokehModelPage) -> None: - # case_sensitive=True by default - text_input = AutocompleteInput(title="title", completions = ["100001", "aAaaaa", "aAaBbb", "AAAaAA", "aAaBbB"]) - - page = bokeh_model_page(text_input) - - el = find_element_for(page.driver, text_input, ".bk-menu") - assert 'display: none;' in el.get_attribute('style') - - # double click to highlight and overwrite old text - el = find_element_for(page.driver, text_input, "input") - enter_text_in_element(page.driver, el, "aAa", click=2, enter=False) - - el = find_element_for(page.driver, text_input, ".bk-menu") - assert 'display: none;' not in el.get_attribute('style') - - items = el.find_elements(By.TAG_NAME, "div") - assert len(items) == 3 - assert items[0].text == "aAaaaa" - assert items[1].text == "aAaBbb" - assert items[2].text == "aAaBbB" - assert "bk-active" in items[0].get_attribute('class') - - el = find_element_for(page.driver, text_input, "input") - enter_text_in_element(page.driver, el, "aAaB", click=2, enter=False) - - el = find_element_for(page.driver, text_input, ".bk-menu") - assert 'display: none;' not in el.get_attribute('style') - - items = el.find_elements(By.TAG_NAME, "div") - assert len(items) == 2 - assert items[0].text == "aAaBbb" - assert items[1].text == "aAaBbB" - assert "bk-active" in items[0].get_attribute('class') - - enter_text_in_element(page.driver, el, Keys.DOWN, click=0, enter=False) - items = el.find_elements(By.TAG_NAME, "div") - assert len(items) == 2 - assert items[0].text == "aAaBbb" - assert items[1].text == "aAaBbB" - assert "bk-active" not in items[0].get_attribute('class') - assert "bk-active" in items[1].get_attribute('class') - - assert page.has_no_console_errors() - - def test_server_restriction_to_list(self, bokeh_server_page: BokehServerPage) -> None: - """Test that input entered manually doesn't end up in the value.""" - text_input = AutocompleteInput(completions = ["aAaBbb"], restrict=True) - - def add_autocomplete(doc): - # note: for some reason, bokeh_server_page requires a 'canvas' in the document - plot = Plot() - doc.add_root(column(text_input,plot)) - - page = bokeh_server_page(add_autocomplete) - - el = find_element_for(page.driver, text_input, "input") - text = "not in completions" - enter_text_in_element(page.driver, el, text, click=1, enter=True) - - assert text_input.value == '' - assert page.has_no_console_errors() - - def test_no_restriction(self, bokeh_model_page: BokehModelPage) -> None: - """Test effect of 'restrict=False' with explicit JS callback""" - text_input = AutocompleteInput(completions = ["aAaBbb", "aAaBbB"], restrict=False) - text_input.js_on_change('value', CustomJS(code=RECORD("value", "cb_obj.value"))) - - page = bokeh_model_page(text_input) - - el = find_element_for(page.driver, text_input, "input") - text = "not in completions" - enter_text_in_element(page.driver, el, text, click=1, enter=True) - - results = page.results - assert results['value'] == text - assert page.has_no_console_errors() - - def test_server_no_restriction(self, bokeh_server_page: BokehServerPage) -> None: - """Test effect of 'restrict=False' without explicit callback.""" - text_input = AutocompleteInput(completions = ["aAaBbb", "aAaBbB"], restrict=False) - - def add_autocomplete(doc): - # note: for some reason, bokeh_server_page requires a 'canvas' in the document - plot = Plot() - doc.add_root(column(text_input,plot)) - - page = bokeh_server_page(add_autocomplete) - - el = find_element_for(page.driver, text_input, "input") - text = "not in completions" - enter_text_in_element(page.driver, el, text, click=1, enter=True) - - assert text_input.value == text - assert page.has_no_console_errors() - - def test_arrow_cannot_escape_menu(self, bokeh_model_page: BokehModelPage) -> None: - text_input = AutocompleteInput(title="title", completions = ["100001", "12344556", "12344557", "3194567289", "209374209374"]) - - page = bokeh_model_page(text_input) - - el = find_element_for(page.driver, text_input, ".bk-menu") - assert 'display: none;' in el.get_attribute('style') - - el = find_element_for(page.driver, text_input, "input") - enter_text_in_element(page.driver, el, "123", click=2, enter=False) - - el = find_element_for(page.driver, text_input, ".bk-menu") - assert 'display: none;' not in el.get_attribute('style') - - items = el.find_elements(By.TAG_NAME, "div") - assert len(items) == 2 - assert items[0].text == "12344556" - assert items[1].text == "12344557" - assert "bk-active" in items[0].get_attribute('class') - assert "bk-active" not in items[1].get_attribute('class') - - # arrow down moves to second item - enter_text_in_element(page.driver, el, Keys.DOWN, click=0, enter=False) - items = el.find_elements(By.TAG_NAME, "div") - assert len(items) == 2 - assert items[0].text == "12344556" - assert items[1].text == "12344557" - assert "bk-active" not in items[0].get_attribute('class') - assert "bk-active" in items[1].get_attribute('class') - - # arrow down again has no effect - enter_text_in_element(page.driver, el, Keys.DOWN, click=0, enter=False) - items = el.find_elements(By.TAG_NAME, "div") - assert len(items) == 2 - assert items[0].text == "12344556" - assert items[1].text == "12344557" - assert "bk-active" not in items[0].get_attribute('class') - assert "bk-active" in items[1].get_attribute('class') - - # arrow up moves to first item - enter_text_in_element(page.driver, el, Keys.UP, click=0, enter=False) - assert len(items) == 2 - assert items[0].text == "12344556" - assert items[1].text == "12344557" - assert "bk-active" in items[0].get_attribute('class') - assert "bk-active" not in items[1].get_attribute('class') - - # arrow up again has no effect - enter_text_in_element(page.driver, el, Keys.UP, click=0, enter=False) - assert len(items) == 2 - assert items[0].text == "12344556" - assert items[1].text == "12344557" - assert "bk-active" in items[0].get_attribute('class') - assert "bk-active" not in items[1].get_attribute('class') - - assert page.has_no_console_errors() - - def test_mouse_hover(self, bokeh_model_page: BokehModelPage) -> None: - text_input = AutocompleteInput(title="title", completions = ["100001", "12344556", "12344557", "3194567289", "209374209374"]) - - page = bokeh_model_page(text_input) - - el = find_element_for(page.driver, text_input, ".bk-menu") - assert 'display: none;' in el.get_attribute('style') - - el = find_element_for(page.driver, text_input, "input") - enter_text_in_element(page.driver, el, "123", click=2, enter=False) - - el = find_element_for(page.driver, text_input, ".bk-menu") - assert 'display: none;' not in el.get_attribute('style') - - items = el.find_elements(By.TAG_NAME, "div") - assert len(items) == 2 - assert items[0].text == "12344556" - assert items[1].text == "12344557" - assert "bk-active" in items[0].get_attribute('class') - assert "bk-active" not in items[1].get_attribute('class') - - # hover over second element - items = el.find_elements(By.TAG_NAME, "div") - hover_element(page.driver, items[1]) - assert len(items) == 2 - assert items[0].text == "12344556" - assert items[1].text == "12344557" - assert "bk-active" not in items[0].get_attribute('class') - assert "bk-active" in items[1].get_attribute('class') - - def test_unrestricted_selection_callback_count(self, bokeh_server_page: BokehServerPage) -> None: - class CallbackCounter: - def __init__(self) -> None: - self.count = 0 - - def increment(self, attr, old, new) -> None: - self.count += 1 - self.new = new - - counter = CallbackCounter() - - input_box = AutocompleteInput(completions = ["100001", "12344556"], restrict=False) - - def unrestricted_input(doc): - input_box.on_change('value', counter.increment) - plot = Plot() - doc.add_root(column(input_box, plot)) - - page = bokeh_server_page(unrestricted_input) - - el = find_element_for(page.driver, input_box, "input") - enter_text_in_element(page.driver, el, "ASDF", enter=True) - assert(counter.count == 1) - assert(counter.new == "ASDF") - - def test_server_on_change_no_round_trip_without_enter_or_click(self, bokeh_server_page: BokehServerPage) -> None: - input_box = AutocompleteInput() - modify_doc, _ = mk_modify_doc(input_box) - page = bokeh_server_page(modify_doc) - - el = find_element_for(page.driver, input_box, "input") - enter_text_in_element(page.driver, el, "pre", enter=False) # not change event if enter is not pressed - - page.eval_custom_action() - - results = page.results - assert results['data']['val'] == ["a", "b"] - - assert page.has_no_console_errors() - - def test_server_on_change_round_trip_full_entry(self, bokeh_server_page: BokehServerPage) -> None: - input_box = AutocompleteInput() - modify_doc, plot = mk_modify_doc(input_box) - page = bokeh_server_page(modify_doc) - - # double click to highlight and overwrite old text - el = find_element_for(page.driver, input_box, "input") - enter_text_in_element(page.driver, el, "100001", click=2) - - page.eval_custom_action() - - results = page.results - assert results['data']['val'] == ["400", "100001"] - - enter_text_in_element(page.driver, el, "12344556", click=2) - - page.eval_custom_action() - - results = page.results - assert results['data']['val'] == ["100001", "12344556"] - - # Check clicking outside input also triggers - enter_text_in_element(page.driver, el, "3194567289", click=2) - page.click_canvas_at_position(plot, 10, 10) - - page.eval_custom_action() - - results = page.results - assert results['data']['val'] == ["12344556", "3194567289"] - - def test_server_on_change_round_trip_partial_entry(self, bokeh_server_page: BokehServerPage) -> None: - input_box = AutocompleteInput() - modify_doc, plot = mk_modify_doc(input_box) - page = bokeh_server_page(modify_doc) - - # double click to highlight and overwrite old text - el = find_element_for(page.driver, input_box, "input") - enter_text_in_element(page.driver, el, "100", click=2) - - page.eval_custom_action() - - results = page.results - assert results['data']['val'] == ["400", "100001"] - - enter_text_in_element(page.driver, el, "123", click=2) - - page.eval_custom_action() - - results = page.results - assert results['data']['val'] == ["100001", "12344556"] - - # Check clicking outside input also triggers - enter_text_in_element(page.driver, el, "319", click=2, enter=False) - page.click_canvas_at_position(plot, 10, 10) - - page.eval_custom_action() - - results = page.results - assert results['data']['val'] == ["12344556", "3194567289"] - - assert page.has_no_console_errors() - - def test_server_on_change_round_trip_menu_entry(self, bokeh_server_page: BokehServerPage) -> None: - input_box = AutocompleteInput() - modify_doc, _ = mk_modify_doc(input_box) - page = bokeh_server_page(modify_doc) - - # double click to highlight and overwrite old text - el = find_element_for(page.driver, input_box, "input") - enter_text_in_element(page.driver, el, "123", click=2, enter=False) - enter_text_in_element(page.driver, el, Keys.DOWN, click=0) - - page.eval_custom_action() - - results = page.results - assert results['data']['val'] == ["400", "12344557"] - - enter_text_in_element(page.driver, el, "123", click=2, enter=False) - - el = find_element_for(page.driver, input_box, ".bk-menu") - items = el.find_elements(By.TAG_NAME, "div") - hover_element(page.driver, items[1]) - items[1].click() - - page.eval_custom_action() - - results = page.results - assert results['data']['val'] == ["400", "12344557"] - - assert page.has_no_console_errors() diff --git a/tests/integration/widgets/test_button.py b/tests/integration/widgets/test_button.py deleted file mode 100644 index c5899c8250d..00000000000 --- a/tests/integration/widgets/test_button.py +++ /dev/null @@ -1,99 +0,0 @@ -#----------------------------------------------------------------------------- -# Copyright (c) Anaconda, Inc. All rights reserved. -# -# Powered by the Bokeh Development Team. -# -# The full license is in the file LICENSE.txt, distributed with this software. -#----------------------------------------------------------------------------- - -#----------------------------------------------------------------------------- -# Boilerplate -#----------------------------------------------------------------------------- -from __future__ import annotations # isort:skip - -import pytest ; pytest - -#----------------------------------------------------------------------------- -# Imports -#----------------------------------------------------------------------------- - -# Bokeh imports -from bokeh.core.enums import ButtonType -from bokeh.layouts import column -from bokeh.models import ( - Button, - ColumnDataSource, - CustomJS, - Plot, - Range1d, - Scatter, -) -from tests.support.plugins.project import BokehModelPage, BokehServerPage -from tests.support.util.selenium import RECORD, find_element_for - -#----------------------------------------------------------------------------- -# Tests -#----------------------------------------------------------------------------- - -pytest_plugins = ( - "tests.support.plugins.project", -) - - -@pytest.mark.selenium -class Test_Button: - def test_displays_label(self, bokeh_model_page: BokehModelPage) -> None: - button = Button(label="label") - - page = bokeh_model_page(button) - - button_el = find_element_for(page.driver, button, ".bk-btn") - assert button_el.text == "label" - - @pytest.mark.parametrize('typ', list(ButtonType)) - def test_displays_button_type(self, typ, bokeh_model_page: BokehModelPage) -> None: - button = Button(button_type=typ) - - page = bokeh_model_page(button) - - button = find_element_for(page.driver, button, ".bk-btn") - assert typ in button.get_attribute('class') - - def test_server_on_event_round_trip(self, bokeh_server_page: BokehServerPage) -> None: - - button = Button() - def modify_doc(doc): - source = ColumnDataSource(dict(x=[1, 2], y=[1, 1])) - plot = Plot(height=400, width=400, x_range=Range1d(0, 1), y_range=Range1d(0, 1), min_border=0) - plot.add_glyph(source, Scatter(x='x', y='y', size=20)) - plot.tags.append(CustomJS(name="custom-action", args=dict(s=source), code=RECORD("data", "s.data"))) - def cb(event): - source.data=dict(x=[10, 20], y=[10, 10]) - button.on_event('button_click', cb) - doc.add_root(column(button, plot)) - - page = bokeh_server_page(modify_doc) - - button_el = find_element_for(page.driver, button, ".bk-btn") - button_el.click() - - page.eval_custom_action() - - results = page.results - assert results == {'data': {'x': [10, 20], 'y': [10, 10]}} - - assert page.has_no_console_errors() - - def test_js_on_event_executes(self, bokeh_model_page: BokehModelPage) -> None: - button = Button() - button.js_on_event('button_click', CustomJS(code=RECORD("clicked", "true"))) - - page = bokeh_model_page(button) - - button_el = find_element_for(page.driver, button, ".bk-btn") - button_el.click() - - results = page.results - assert results == {'clicked': True} - - assert page.has_no_console_errors() diff --git a/tests/integration/widgets/test_checkbox_button_group.py b/tests/integration/widgets/test_checkbox_button_group.py deleted file mode 100644 index 73d2d127562..00000000000 --- a/tests/integration/widgets/test_checkbox_button_group.py +++ /dev/null @@ -1,102 +0,0 @@ -#----------------------------------------------------------------------------- -# Copyright (c) Anaconda, Inc. All rights reserved. -# -# Powered by the Bokeh Development Team. -# -# The full license is in the file LICENSE.txt, distributed with this software. -#----------------------------------------------------------------------------- - -#----------------------------------------------------------------------------- -# Boilerplate -#----------------------------------------------------------------------------- -from __future__ import annotations # isort:skip - -import pytest ; pytest - -#----------------------------------------------------------------------------- -# Imports -#----------------------------------------------------------------------------- - -# Bokeh imports -from bokeh.layouts import column -from bokeh.models import ( - CheckboxButtonGroup, - ColumnDataSource, - CustomJS, - Plot, - Range1d, - Scatter, -) -from tests.support.plugins.project import BokehModelPage, BokehServerPage -from tests.support.util.selenium import RECORD, find_element_for - -#----------------------------------------------------------------------------- -# Tests -#----------------------------------------------------------------------------- - -pytest_plugins = ( - "tests.support.plugins.project", -) - -LABELS = ["Option 1", "Option 2", "Option 3"] - - -@pytest.mark.selenium -class Test_CheckboxButtonGroup: - def test_server_on_change_round_trip(self, bokeh_server_page: BokehServerPage) -> None: - group = CheckboxButtonGroup(labels=LABELS) - def modify_doc(doc): - source = ColumnDataSource(dict(x=[1, 2], y=[1, 1], val=["a", "b"])) - plot = Plot(height=400, width=400, x_range=Range1d(0, 1), y_range=Range1d(0, 1), min_border=0) - plot.add_glyph(source, Scatter(x='x', y='y', size=20)) - plot.tags.append(CustomJS(name="custom-action", args=dict(s=source), code=RECORD("data", "s.data"))) - def cb(event): - source.data['val'] = ([*group.active, 0, 0])[:2] # keep col length at 2, padded with zero - group.on_event('button_click', cb) - doc.add_root(column(group, plot)) - - page = bokeh_server_page(modify_doc) - - el = find_element_for(page.driver, group, ".bk-btn:nth-child(3)") - el.click() - - page.eval_custom_action() - - results = page.results - assert results['data']['val'] == [2, 0] - - el = find_element_for(page.driver, group, ".bk-btn:nth-child(1)") - el.click() - - page.eval_custom_action() - - results = page.results - assert results['data']['val'] == [0, 2] - - assert page.has_no_console_errors() - - def test_js_on_change_executes(self, bokeh_model_page: BokehModelPage) -> None: - group = CheckboxButtonGroup(labels=LABELS) - group.js_on_event('button_click', CustomJS(code=RECORD("active", "cb_obj.origin.active"))) - - page = bokeh_model_page(group) - - el = find_element_for(page.driver, group, ".bk-btn:nth-child(3)") - el.click() - - results = page.results - assert results['active'] == [2] - - el = find_element_for(page.driver, group, ".bk-btn:nth-child(1)") - el.click() - - results = page.results - assert results['active'] == [0, 2] - - el = find_element_for(page.driver, group, ".bk-btn:nth-child(3)") - el.click() - - results = page.results - assert results['active'] == [0] - - assert page.has_no_console_errors() diff --git a/tests/integration/widgets/test_checkbox_group.py b/tests/integration/widgets/test_checkbox_group.py deleted file mode 100644 index 0fe572ecdd2..00000000000 --- a/tests/integration/widgets/test_checkbox_group.py +++ /dev/null @@ -1,120 +0,0 @@ -#----------------------------------------------------------------------------- -# Copyright (c) Anaconda, Inc. All rights reserved. -# -# Powered by the Bokeh Development Team. -# -# The full license is in the file LICENSE.txt, distributed with this software. -#----------------------------------------------------------------------------- - -#----------------------------------------------------------------------------- -# Boilerplate -#----------------------------------------------------------------------------- -from __future__ import annotations # isort:skip - -import pytest ; pytest - -#----------------------------------------------------------------------------- -# Imports -#----------------------------------------------------------------------------- - -# External imports -from selenium.webdriver.common.by import By - -# Bokeh imports -from bokeh.layouts import column -from bokeh.models import ( - CheckboxGroup, - ColumnDataSource, - CustomJS, - Plot, - Range1d, - Scatter, -) -from tests.support.plugins.project import BokehModelPage, BokehServerPage -from tests.support.util.selenium import RECORD, find_element_for, find_elements_for - -#----------------------------------------------------------------------------- -# Tests -#----------------------------------------------------------------------------- - -pytest_plugins = ( - "tests.support.plugins.project", -) - -LABELS = ["Option 1", "Option 2", "Option 3"] - - -@pytest.mark.selenium -class Test_CheckboxGroup: - @pytest.mark.parametrize('inline', [True, False]) - def test_displays_options_list_of_string_labels_setting_inline(self, inline, bokeh_model_page: BokehModelPage) -> None: - group = CheckboxGroup(labels=LABELS, inline=inline) - - page = bokeh_model_page(group) - - labels = find_elements_for(page.driver, group, "label") - assert len(labels) == 3 - - for i, label in enumerate(labels): - assert label.text == LABELS[i] - input = label.find_element(By.TAG_NAME, 'input') - assert input.get_attribute('value') == str(i) - assert input.get_attribute('type') == 'checkbox' - - def test_server_on_change_round_trip(self, bokeh_server_page: BokehServerPage) -> None: - group = CheckboxGroup(labels=LABELS) - def modify_doc(doc): - source = ColumnDataSource(dict(x=[1, 2], y=[1, 1], val=["a", "b"])) - plot = Plot(height=400, width=400, x_range=Range1d(0, 1), y_range=Range1d(0, 1), min_border=0) - plot.add_glyph(source, Scatter(x='x', y='y', size=20)) - plot.tags.append(CustomJS(name="custom-action", args=dict(s=source), code=RECORD("data", "s.data"))) - def cb(attr, old, new): - source.data['val'] = ([*group.active, 0, 0])[:2] # keep col length at 2, padded with zero - group.on_change('active', cb) - doc.add_root(column(group, plot)) - - page = bokeh_server_page(modify_doc) - - el = find_element_for(page.driver, group, 'input[value="2"]') - el.click() - - page.eval_custom_action() - - results = page.results - assert results['data']['val'] == [2, 0] - - el = find_element_for(page.driver, group, 'input[value="0"]') - el.click() - - page.eval_custom_action() - - results = page.results - assert results['data']['val'] == [0, 2] - - assert page.has_no_console_errors() - - def test_js_on_change_executes(self, bokeh_model_page: BokehModelPage) -> None: - group = CheckboxGroup(labels=LABELS) - group.js_on_change('active', CustomJS(code=RECORD("active", "cb_obj.active"))) - - page = bokeh_model_page(group) - - el = find_element_for(page.driver, group, 'input[value="2"]') - el.click() - - results = page.results - assert results['active'] == [2] - - el = find_element_for(page.driver, group, 'input[value="0"]') - el.click() - - results = page.results - assert results['active'] == [0, 2] - - el = find_element_for(page.driver, group, 'input[value="2"]') - el.click() - - results = page.results - assert results['active'] == [0] - - assert page.has_no_console_errors() diff --git a/tests/integration/widgets/test_color_picker.py b/tests/integration/widgets/test_color_picker.py deleted file mode 100644 index 7dd70a4387a..00000000000 --- a/tests/integration/widgets/test_color_picker.py +++ /dev/null @@ -1,108 +0,0 @@ -#----------------------------------------------------------------------------- -# Copyright (c) Anaconda, Inc. All rights reserved. -# -# Powered by the Bokeh Development Team. -# -# The full license is in the file LICENSE.txt, distributed with this software. -#----------------------------------------------------------------------------- - -#----------------------------------------------------------------------------- -# Boilerplate -#----------------------------------------------------------------------------- -from __future__ import annotations # isort:skip - -import pytest ; pytest - -#----------------------------------------------------------------------------- -# Imports -#----------------------------------------------------------------------------- - -# Bokeh imports -from bokeh.layouts import column -from bokeh.models import ( - Circle, - ColorPicker, - ColumnDataSource, - CustomJS, - Plot, - Range1d, -) -from tests.support.plugins.project import BokehModelPage, BokehServerPage -from tests.support.util.selenium import RECORD, find_element_for - -#----------------------------------------------------------------------------- -# Tests -#----------------------------------------------------------------------------- - -pytest_plugins = ( - "tests.support.plugins.project", -) - -def mk_modify_doc(colorpicker: ColorPicker): - def modify_doc(doc): - source = ColumnDataSource(dict(x=[1, 2], y=[1, 1], val=["a", "b"])) - plot = Plot(height=400, width=400, x_range=Range1d(0, 1), y_range=Range1d(0, 1), min_border=0) - - plot.add_glyph(source, Circle(x='x', y='y')) - plot.tags.append(CustomJS(name="custom-action", args=dict(s=source), code=RECORD("data", "s.data"))) - - def cb(attr, old, new): - source.data['val'] = [old.lower(), new.lower()] # ensure lowercase of hexa strings - - colorpicker.on_change('color', cb) - doc.add_root(column(colorpicker, plot)) - return doc - return modify_doc - -def enter_value_in_color_picker(driver, el, color): - driver.execute_script(f"arguments[0].value = '{color}'", el) - driver.execute_script("arguments[0].dispatchEvent(new Event('change'))", el) - - -@pytest.mark.selenium -class Test_ColorPicker: - def test_display_color_input(self, bokeh_model_page: BokehModelPage) -> None: - colorpicker = ColorPicker() - page = bokeh_model_page(colorpicker) - - el = find_element_for(page.driver, colorpicker, "input") - assert el.get_attribute('type') == "color" - - assert page.has_no_console_errors() - - def test_displays_title(self, bokeh_model_page: BokehModelPage) -> None: - colorpicker = ColorPicker(title="title") - page = bokeh_model_page(colorpicker) - - el = find_element_for(page.driver, colorpicker, "label") - assert el.text == "title" - - el = find_element_for(page.driver, colorpicker, "input") - assert el.get_attribute('type') == "color" - - assert page.has_no_console_errors() - - def test_input_value(self, bokeh_model_page: BokehModelPage) -> None: - colorpicker = ColorPicker(color="red") - page = bokeh_model_page(colorpicker) - - el = find_element_for(page.driver, colorpicker, "input") - - assert el.get_attribute('value') == '#ff0000' - - assert page.has_no_console_errors() - - def test_server_on_change_round_trip(self, bokeh_server_page: BokehServerPage) -> None: - colorpicker = ColorPicker(color="red") - page = bokeh_server_page(mk_modify_doc(colorpicker)) - - el = find_element_for(page.driver, colorpicker, "input") - - # new value - enter_value_in_color_picker(page.driver, el, '#0000ff') - page.eval_custom_action() - results = page.results - assert results['data']['val'] == ['#ff0000', '#0000ff'] - - # XXX (bev) disabled until https://github.com/bokeh/bokeh/issues/7970 is resolved - # assert page.has_no_console_errors() diff --git a/tests/integration/widgets/test_copy_paste.py b/tests/integration/widgets/test_copy_paste.py deleted file mode 100644 index 9dc764152a8..00000000000 --- a/tests/integration/widgets/test_copy_paste.py +++ /dev/null @@ -1,77 +0,0 @@ -#----------------------------------------------------------------------------- -# Copyright (c) Anaconda, Inc. All rights reserved. -# -# Powered by the Bokeh Development Team. -# -# The full license is in the file LICENSE.txt, distributed with this software. -#----------------------------------------------------------------------------- - -#----------------------------------------------------------------------------- -# Boilerplate -#----------------------------------------------------------------------------- -from __future__ import annotations # isort:skip - -import pytest ; pytest - -#----------------------------------------------------------------------------- -# Imports -#----------------------------------------------------------------------------- - -# Standard library imports -from time import sleep - -# Bokeh imports -from bokeh.layouts import column -from bokeh.models import ( - ColumnDataSource, - DataTable, - Div, - TableColumn, -) -from tests.support.plugins.project import BokehModelPage -from tests.support.util.selenium import ( - copy_table_rows, - enter_text_with_click_enter, - find_element_for, - paste_values, -) - -#----------------------------------------------------------------------------- -# Tests -#----------------------------------------------------------------------------- - -pytest_plugins = ( - "tests.support.plugins.project", -) - - -@pytest.mark.selenium -class Test_CopyPaste: - def test_copy_paste_to_textarea(self, bokeh_model_page: BokehModelPage) -> None: - source = ColumnDataSource(dict(x=[1, 2], y=[1, 1])) - columns = [TableColumn(field='x', title='x'), TableColumn(field='y', title='y')] - table = DataTable(source=source, columns=columns, editable=False, width=600) - text_area = Div(text='') - - page = bokeh_model_page(column(table, text_area)) - - # Use reversed order to get the correct order - copy_table_rows(page.driver, table, [2, 1]) - - # Copy is a little slow - sleep(0.1) - - element = find_element_for(page.driver, text_area, "textarea") - - # Selenium doesn't paste until we write something to the element first - # textarea works like a cell - enter_text_with_click_enter(page.driver, element, 'PASTED:') - - paste_values(page.driver, element) - - result = element.get_attribute('value') - - # The textarea now contains the content in the datatable - assert result == '\nPASTED:\n0\t1\t1\n1\t2\t1\n' - - assert page.has_no_console_errors() diff --git a/tests/integration/widgets/test_datepicker.py b/tests/integration/widgets/test_datepicker.py deleted file mode 100644 index b0637a669e8..00000000000 --- a/tests/integration/widgets/test_datepicker.py +++ /dev/null @@ -1,233 +0,0 @@ -#----------------------------------------------------------------------------- -# Copyright (c) Anaconda, Inc. All rights reserved. -# -# Powered by the Bokeh Development Team. -# -# The full license is in the file LICENSE.txt, distributed with this software. -#----------------------------------------------------------------------------- - -#----------------------------------------------------------------------------- -# Boilerplate -#----------------------------------------------------------------------------- -from __future__ import annotations # isort:skip - -import pytest ; pytest - -#----------------------------------------------------------------------------- -# Imports -#----------------------------------------------------------------------------- - -# Standard library imports -from datetime import date - -# Bokeh imports -from bokeh.layouts import column -from bokeh.models import ( - ColumnDataSource, - CustomJS, - DatePicker, - Plot, - Range1d, - Scatter, -) -from tests.support.plugins.project import BokehModelPage, BokehServerPage -from tests.support.util.selenium import RECORD, find_element_for - -#----------------------------------------------------------------------------- -# Tests -#----------------------------------------------------------------------------- - -pytest_plugins = ( - "tests.support.plugins.project", -) - - -@pytest.mark.selenium -class Test_DatePicker: - def test_basic(self, bokeh_model_page: BokehModelPage) -> None: - dp = DatePicker(title='Select date', value=date(2019, 9, 20), min_date=date(2019, 9, 1), max_date="2019-09-30") - - page = bokeh_model_page(dp) - - el = find_element_for(page.driver, dp, "label") - assert el.text == "Select date" - - el = find_element_for(page.driver, dp, '.flatpickr-calendar') - assert "inline" not in el.get_attribute("class") - - assert page.has_no_console_errors() - - def test_inline(self, bokeh_model_page: BokehModelPage) -> None: - dp = DatePicker(title='Select date', value=date(2019, 9, 20), min_date=date(2019, 9, 1), max_date="2019-09-30", inline=True) - - page = bokeh_model_page(dp) - - el = find_element_for(page.driver, dp, "label") - assert el.text == "Select date" - - el = find_element_for(page.driver, dp, '.flatpickr-calendar') - assert "inline" in el.get_attribute("class") - - assert page.has_no_console_errors() - - def test_widget_disabled(self, bokeh_model_page: BokehModelPage) -> None: - dp = DatePicker(title='Select date', value=date(2019, 9, 20), min_date=date(2019, 9, 1), max_date="2019-09-30", disabled=True) - - page = bokeh_model_page(dp) - - el = find_element_for(page.driver, dp, '.flatpickr-input') - assert el.get_attribute("disabled") == "true" - - assert page.has_no_console_errors() - - def test_disabled_dates(self, bokeh_model_page: BokehModelPage) -> None: - dp = DatePicker(title='Select date', value=date(2019, 9, 20), min_date=date(2019, 9, 1), max_date="2019-09-30", - disabled_dates=["2019-09-14", ("2019-09-16", date(2019, 9, 18))]) - - page = bokeh_model_page(dp) - - el = find_element_for(page.driver, dp, "label") - el.click() - - # not disabled - el = find_element_for(page.driver, dp, 'span[aria-label="September 13, 2019"]') - assert "flatpickr-disabled" not in el.get_attribute("class") - - el = find_element_for(page.driver, dp, 'span[aria-label="September 14, 2019"]') - assert "flatpickr-disabled" in el.get_attribute("class") - - # not disabled - el = find_element_for(page.driver, dp, 'span[aria-label="September 15, 2019"]') - assert "flatpickr-disabled" not in el.get_attribute("class") - - el = find_element_for(page.driver, dp, 'span[aria-label="September 16, 2019"]') - assert "flatpickr-disabled" in el.get_attribute("class") - - el = find_element_for(page.driver, dp, 'span[aria-label="September 17, 2019"]') - assert "flatpickr-disabled" in el.get_attribute("class") - - el = find_element_for(page.driver, dp, 'span[aria-label="September 18, 2019"]') - assert "flatpickr-disabled" in el.get_attribute("class") - - # not disabled - el = find_element_for(page.driver, dp, 'span[aria-label="September 19, 2019"]') - assert "flatpickr-disabled" not in el.get_attribute("class") - - assert page.has_no_console_errors() - - def test_enabled_dates(self, bokeh_model_page: BokehModelPage) -> None: - dp = DatePicker(title='Select date', value=date(2019, 9, 20), min_date=date(2019, 9, 1), max_date="2019-09-30", - enabled_dates=["2019-09-14", ("2019-09-16", date(2019, 9, 18))]) - - page = bokeh_model_page(dp) - - el = find_element_for(page.driver, dp, "label") - el.click() - - # not enabled - el = find_element_for(page.driver, dp, 'span[aria-label="September 13, 2019"]') - assert "flatpickr-disabled" in el.get_attribute("class") - - el = find_element_for(page.driver, dp, 'span[aria-label="September 14, 2019"]') - assert "flatpickr-disabled" not in el.get_attribute("class") - - # not enabled - el = find_element_for(page.driver, dp, 'span[aria-label="September 15, 2019"]') - assert "flatpickr-disabled" in el.get_attribute("class") - - el = find_element_for(page.driver, dp, 'span[aria-label="September 16, 2019"]') - assert "flatpickr-disabled" not in el.get_attribute("class") - - el = find_element_for(page.driver, dp, 'span[aria-label="September 17, 2019"]') - assert "flatpickr-disabled" not in el.get_attribute("class") - - el = find_element_for(page.driver, dp, 'span[aria-label="September 18, 2019"]') - assert "flatpickr-disabled" not in el.get_attribute("class") - - # not enabled - el = find_element_for(page.driver, dp, 'span[aria-label="September 19, 2019"]') - assert "flatpickr-disabled" in el.get_attribute("class") - - assert page.has_no_console_errors() - - def _test_js_on_change_executes(self, bokeh_model_page: BokehModelPage) -> None: - dp = DatePicker(title='Select date', value=date(2019, 9, 20), min_date=date(2019, 9, 1), max_date="2019-09-30") - dp.js_on_change('value', CustomJS(code=RECORD("value", "cb_obj.value"))) - - page = bokeh_model_page(dp) - - el = find_element_for(page.driver, dp, "input") - el.click() - el.click() - - el = find_element_for(page.driver, dp, 'span[aria-label="September 16, 2019"]') - assert el.is_displayed() - el.click() - - results = page.results - assert results['value'] == '2019-09-16' - - el = find_element_for(page.driver, dp, '.bk-input') - assert el.get_attribute('value') == '2019-09-16' - - assert page.has_no_console_errors() - - def _test_server_on_change_round_trip(self, bokeh_server_page: BokehServerPage) -> None: - dp = DatePicker(title='Select date', value=date(2019, 9, 20), min_date=date(2019, 9, 1), max_date="2019-09-30") - - def modify_doc(doc): - source = ColumnDataSource(dict(x=[1, 2], y=[1, 1], val=["a", "b"])) - plot = Plot(height=400, width=400, x_range=Range1d(0, 1), y_range=Range1d(0, 1), min_border=0) - plot.tags.append(CustomJS(name="custom-action", args=dict(s=source), code=RECORD("data", "s.data"))) - plot.add_glyph(source, Scatter(x='x', y='y', size=20)) - def cb(attr, old, new): - source.data['val'] = [old, new] - dp.on_change('value', cb) - doc.add_root(column(dp, plot)) - - page = bokeh_server_page(modify_doc) - - el = find_element_for(page.driver, dp, "input") - el.click() - el.click() - - el = find_element_for(page.driver, dp, 'span[aria-label="September 16, 2019"]') - assert el.is_displayed() - el.click() - - page.eval_custom_action() - - results = page.results - assert results['data']['val'] == ['2019-09-20', '2019-09-16'] - - def _test_server_update_disabled(self, bokeh_server_page: BokehServerPage) -> None: - dp = DatePicker(title='Select date', value=date(2019, 9, 20), min_date=date(2019, 9, 1), max_date="2019-09-30") - - def modify_doc(doc): - source = ColumnDataSource(dict(x=[1, 2], y=[1, 1], val=["a", "b"])) - plot = Plot(height=400, width=400, x_range=Range1d(0, 1), y_range=Range1d(0, 1), min_border=0) - plot.tags.append(CustomJS(name="custom-action", args=dict(s=source), code=RECORD("data", "s.data"))) - plot.add_glyph(source, Scatter(x='x', y='y', size=20)) - def cb(attr, old, new): - source.data['val'] = [old, new] - dp.disabled_dates = ["2019-09-15"] - dp.on_change('value', cb) - doc.add_root(column(dp, plot)) - - page = bokeh_server_page(modify_doc) - - el = find_element_for(page.driver, dp, "input") - el.click() - el.click() - - el = find_element_for(page.driver, dp, 'span[aria-label="September 16, 2019"]') - assert el.is_displayed() - el.click() - - page.eval_custom_action() - - el = find_element_for(page.driver, dp, 'span[aria-label="September 15, 2019"]') - assert "flatpickr-disabled" in el.get_attribute("class") - - results = page.results - assert results['data']['val'] == ['2019-09-20', '2019-09-16'] diff --git a/tests/integration/widgets/test_daterange_slider.py b/tests/integration/widgets/test_daterange_slider.py deleted file mode 100644 index 48489d7b1fe..00000000000 --- a/tests/integration/widgets/test_daterange_slider.py +++ /dev/null @@ -1,193 +0,0 @@ -#----------------------------------------------------------------------------- -# Copyright (c) Anaconda, Inc. All rights reserved. -# -# Powered by the Bokeh Development Team. -# -# The full license is in the file LICENSE.txt, distributed with this software. -#----------------------------------------------------------------------------- - -#----------------------------------------------------------------------------- -# Boilerplate -#----------------------------------------------------------------------------- -from __future__ import annotations # isort:skip - -import pytest ; pytest - -#----------------------------------------------------------------------------- -# Imports -#----------------------------------------------------------------------------- - -# Standard library imports -from datetime import date, datetime, timedelta -from time import sleep - -# Bokeh imports -from bokeh.layouts import column -from bokeh.models import ( - ColumnDataSource, - CustomJS, - DateRangeSlider, - Plot, - Range1d, - Scatter, -) -from tests.support.plugins.project import BokehModelPage, BokehServerPage -from tests.support.util.selenium import ( - RECORD, - drag_range_slider, - find_elements_for, - get_slider_bar_color, - get_slider_title_text, - get_slider_title_value, -) - -#----------------------------------------------------------------------------- -# Tests -#----------------------------------------------------------------------------- - -pytest_plugins = ( - "tests.support.plugins.project", -) - -start = date(2017, 8, 3) -end = date(2017, 8, 10) -value = (start + timedelta(days=1), end - timedelta(days=1)) - - -@pytest.mark.selenium -class Test_DateRangeSlider: - def test_display(self, bokeh_model_page: BokehModelPage) -> None: - slider = DateRangeSlider(start=start, end=end, value=value, width=300) - - page = bokeh_model_page(slider) - - children = find_elements_for(page.driver, slider, "div.bk-input-group > div") - assert len(children) == 2 - - assert page.has_no_console_errors() - - def test_displays_title(self, bokeh_model_page: BokehModelPage) -> None: - slider = DateRangeSlider(start=start, end=end, value=value, width=300) - - page = bokeh_model_page(slider) - - children = find_elements_for(page.driver, slider, "div.bk-input-group > div") - assert len(children) == 2 - - assert get_slider_title_text(page.driver, slider) == '04 Aug 2017 .. 09 Aug 2017' - assert get_slider_title_value(page.driver, slider) == '04 Aug 2017 .. 09 Aug 2017' - - assert page.has_no_console_errors() - - - def test_title_updates(self, bokeh_model_page: BokehModelPage) -> None: - slider = DateRangeSlider(start=start, end=end, value=value, width=300) - - page = bokeh_model_page(slider) - - assert get_slider_title_value(page.driver, slider) == "04 Aug 2017 .. 09 Aug 2017" - - drag_range_slider(page.driver, slider, "lower", 50) - val = get_slider_title_value(page.driver, slider).split(" .. ")[0] - assert val > "04 Aug 2017" - - drag_range_slider(page.driver, slider, "lower", -70) - val = get_slider_title_value(page.driver, slider).split(" .. ")[0] - assert val == "03 Aug 2017" - - assert page.has_no_console_errors() - - def test_displays_bar_color(self, bokeh_model_page: BokehModelPage) -> None: - slider = DateRangeSlider(start=start, end=end, value=value, width=300, bar_color="red") - - page = bokeh_model_page(slider) - - children = find_elements_for(page.driver, slider, "div.bk-input-group > div") - assert len(children) == 2 - - assert get_slider_bar_color(page.driver, slider) == "rgba(255, 0, 0, 1)" - - assert page.has_no_console_errors() - - def test_js_on_change_executes(self, bokeh_model_page: BokehModelPage) -> None: - slider = DateRangeSlider(start=start, end=end, value=value, width=300) - slider.js_on_change('value', CustomJS(code=RECORD("value", "cb_obj.value"))) - - page = bokeh_model_page(slider) - - drag_range_slider(page.driver, slider, "lower", 50) - - results = page.results - assert datetime.fromtimestamp(results['value'][0]/1000) > datetime(*date.fromisoformat("2017-08-04").timetuple()[:3]) - - drag_range_slider(page.driver, slider, "upper", -70) - assert datetime.fromtimestamp(results['value'][1]/1000) < datetime(*date.fromisoformat("2017-08-09").timetuple()[:3]) - - assert page.has_no_console_errors() - - def test_server_on_change_round_trip(self, bokeh_server_page: BokehServerPage) -> None: - slider = DateRangeSlider(start=start, end=end, value=value, width=300) - - def modify_doc(doc): - source = ColumnDataSource(dict(x=[1, 2], y=[1, 1], val=["a", "b"])) - plot = Plot(height=400, width=400, x_range=Range1d(0, 1), y_range=Range1d(0, 1), min_border=0) - plot.add_glyph(source, Scatter(x='x', y='y', size=20)) - plot.tags.append(CustomJS(name="custom-action", args=dict(s=source), code=RECORD("data", "s.data"))) - - def cb(attr, old, new): - source.data['val'] = [slider.value_as_date[0].isoformat(), slider.value_as_date[1].isoformat()] - - slider.on_change('value', cb) - doc.add_root(column(slider, plot)) - - page = bokeh_server_page(modify_doc) - - drag_range_slider(page.driver, slider, "lower", 50) - - page.eval_custom_action() - results = page.results - new = results['data']['val'] - assert new[0] > '2017-08-04' - - drag_range_slider(page.driver, slider, "upper", -50) - - - page.eval_custom_action() - results = page.results - new = results['data']['val'] - assert new[1] < '2017-08-09' - -# # XXX (bev) skip keypress part of test until it can be fixed -# # handle = find_element_for(page.driver, slider, ".noUi-handle-lower") -# # select_element_and_press_key(page.driver, handle, Keys.ARROW_RIGHT) - -# # page.eval_custom_action() -# # results = page.results -# # old, new = results['data']['val'] -# # assert float(new[0]) >= 1 - -# # XXX (bev) disabled until https://github.com/bokeh/bokeh/issues/7970 is resolved -# # assert page.has_no_console_errors() - - def test_server_bar_color_updates(self, bokeh_server_page: BokehServerPage) -> None: - slider = DateRangeSlider(start=start, end=end, value=value, width=300, bar_color="red") - - def modify_doc(doc): - plot = Plot(height=400, width=400, x_range=Range1d(0, 1), y_range=Range1d(0, 1), min_border=0) - - def cb(attr, old, new): - slider.bar_color = "rgba(255, 255, 0, 1)" - - slider.on_change('value', cb) - doc.add_root(column(slider, plot)) - - page = bokeh_server_page(modify_doc) - - drag_range_slider(page.driver, slider, "lower", 150) - - sleep(1) # noUiSlider does a transition that takes some time - - assert get_slider_bar_color(page.driver, slider) == "rgba(255, 255, 0, 1)" - - # XXX (bev) disabled until https://github.com/bokeh/bokeh/issues/7970 is resolved - # assert page.has_no_console_errors() diff --git a/tests/integration/widgets/test_dateslider.py b/tests/integration/widgets/test_dateslider.py deleted file mode 100644 index aa74fc70df4..00000000000 --- a/tests/integration/widgets/test_dateslider.py +++ /dev/null @@ -1,244 +0,0 @@ -#----------------------------------------------------------------------------- -# Copyright (c) Anaconda, Inc. All rights reserved. -# -# Powered by the Bokeh Development Team. -# -# The full license is in the file LICENSE.txt, distributed with this software. -#----------------------------------------------------------------------------- - -#----------------------------------------------------------------------------- -# Boilerplate -#----------------------------------------------------------------------------- -from __future__ import annotations # isort:skip - -import pytest ; pytest - -#----------------------------------------------------------------------------- -# Imports -#----------------------------------------------------------------------------- - -# Standard library imports -from datetime import date, datetime, timedelta -from time import sleep - -# Bokeh imports -from bokeh.layouts import column -from bokeh.models import ( - ColumnDataSource, - CustomJS, - DateSlider, - Plot, - Range1d, - Scatter, -) -from tests.support.plugins.project import BokehModelPage, BokehServerPage -from tests.support.util.selenium import ( - RECORD, - drag_slider, - find_elements_for, - get_slider_bar_color, - get_slider_title_text, - get_slider_title_value, -) - -#----------------------------------------------------------------------------- -# Tests -#----------------------------------------------------------------------------- - -pytest_plugins = ( - "tests.support.plugins.project", -) - -start = date(2017, 8, 3) -end = date(2017, 8, 10) -value = start + timedelta(days=1) - - -@pytest.mark.selenium -class Test_DateSlider: - def test_display(self, bokeh_model_page: BokehModelPage) -> None: - slider = DateSlider(start=start, end=end, value=value, width=300) - - page = bokeh_model_page(slider) - - children = find_elements_for(page.driver, slider, "div.bk-input-group > div") - assert len(children) == 2 - - assert page.has_no_console_errors() - - def test_displays_title(self, bokeh_model_page: BokehModelPage) -> None: - slider = DateSlider(start=start, end=end, value=value, width=300) - - page = bokeh_model_page(slider) - - children = find_elements_for(page.driver, slider, "div.bk-input-group > div") - assert len(children) == 2 - - assert get_slider_title_text(page.driver, slider) == "04 Aug 2017" - assert get_slider_title_value(page.driver, slider) == "04 Aug 2017" - - assert page.has_no_console_errors() - - def test_title_updates(self, bokeh_model_page: BokehModelPage) -> None: - slider = DateSlider(start=start, end=end, value=value, width=300) - - page = bokeh_model_page(slider) - - assert get_slider_title_value(page.driver, slider) == "04 Aug 2017" - - drag_slider(page.driver, slider, 50) - assert get_slider_title_value(page.driver, slider) > "04 Aug 2017" - - drag_slider(page.driver, slider, -70) - assert get_slider_title_value(page.driver, slider) == "03 Aug 2017" - - assert page.has_no_console_errors() - - def test_displays_bar_color(self, bokeh_model_page: BokehModelPage) -> None: - slider = DateSlider(start=start, end=end, value=value, width=300, bar_color="red") - - page = bokeh_model_page(slider) - - children = find_elements_for(page.driver, slider, "div.bk-input-group > div") - assert len(children) == 2 - - assert get_slider_bar_color(page.driver, slider) == "rgba(255, 0, 0, 1)" - - assert page.has_no_console_errors() - - def test_js_on_change_executes(self, bokeh_model_page: BokehModelPage) -> None: - slider = DateSlider(start=start, end=end, value=value, width=300) - slider.js_on_change('value', CustomJS(code=RECORD("value", "cb_obj.value"))) - - page = bokeh_model_page(slider) - - drag_slider(page.driver, slider, 150) - - results = page.results - assert datetime.fromtimestamp(results['value']/1000) > datetime(*date.fromisoformat("2017-08-04").timetuple()[:3]) - - assert page.has_no_console_errors() - - def test_server_on_change_round_trip(self, bokeh_server_page: BokehServerPage) -> None: - slider = DateSlider(start=start, end=end, value=value, width=300, step=1) - - def modify_doc(doc): - source = ColumnDataSource(dict(x=[1, 2], y=[1, 1], val=["a", "b"])) - plot = Plot(height=400, width=400, x_range=Range1d(0, 1), y_range=Range1d(0, 1), min_border=0) - plot.add_glyph(source, Scatter(x='x', y='y', size=20)) - plot.tags.append(CustomJS(name="custom-action", args=dict(s=source), code=RECORD("data", "s.data"))) - - def cb(attr, old, new): - iso_date = slider.value_as_date.isoformat() - source.data['val'] = [iso_date, iso_date] - - slider.on_change('value', cb) - doc.add_root(column(slider, plot)) - - page = bokeh_server_page(modify_doc) - - drag_slider(page.driver, slider, 50) - - page.eval_custom_action() - results = page.results - new = results['data']['val'] - assert new[0] > '2017-08-04' - - drag_slider(page.driver, slider, -70) - - page.eval_custom_action() - results = page.results - new = results['data']['val'] - assert new[0] == '2017-08-03' - - # XXX (bev) skip keypress part of test until it can be fixed - # handle = find_element_for(page.driver, slider, ".noUi-handle") - # select_element_and_press_key(page.driver, handle, Keys.ARROW_RIGHT) - - # page.eval_custom_action() - # results = page.results - # old, new = results['data']['val'] - # assert float(new) == 1 - - # XXX (bev) disabled until https://github.com/bokeh/bokeh/issues/7970 is resolved - # assert page.has_no_console_errors() - - def test_server_callback_value_vs_value_throttled(self, bokeh_server_page: BokehServerPage) -> None: - junk = dict(v=0, vt=0) - slider = DateSlider(start=start, end=end, value=value, width=300) - - def modify_doc(doc): - plot = Plot(height=400, width=400, x_range=Range1d(0, 1), y_range=Range1d(0, 1), min_border=0) - - def cbv(attr, old, new): junk['v'] += 1 - def cbvt(attr, old, new): junk['vt'] += 1 - - slider.on_change('value', cbv) - slider.on_change('value_throttled', cbvt) - doc.add_root(column(slider, plot)) - - page = bokeh_server_page(modify_doc) - - drag_slider(page.driver, slider, 30, release=False) - sleep(1) # noUiSlider does a transition that takes some time - - drag_slider(page.driver, slider, 30, release=False) - sleep(1) # noUiSlider does a transition that takes some time - - drag_slider(page.driver, slider, 30, release=False) - sleep(1) # noUiSlider does a transition that takes some time - - drag_slider(page.driver, slider, 30, release=True) - sleep(1) # noUiSlider does a transition that takes some time - - assert junk['v'] == 4 - assert junk['vt'] == 1 - - # XXX (bev) disabled until https://github.com/bokeh/bokeh/issues/7970 is resolved - # assert page.has_no_console_errors() - - def test_server_bar_color_updates(self, bokeh_server_page: BokehServerPage) -> None: - slider = DateSlider(start=start, end=end, value=value, width=300, bar_color="red") - - def modify_doc(doc): - plot = Plot(height=400, width=400, x_range=Range1d(0, 1), y_range=Range1d(0, 1), min_border=0) - - def cb(attr, old, new): - slider.bar_color = "rgba(255, 255, 0, 1)" - - slider.on_change('value', cb) - doc.add_root(column(slider, plot)) - - page = bokeh_server_page(modify_doc) - - drag_slider(page.driver, slider, 150) - - sleep(1) # noUiSlider does a transition that takes some time - - assert get_slider_bar_color(page.driver, slider) == "rgba(255, 255, 0, 1)" - - # XXX (bev) disabled until https://github.com/bokeh/bokeh/issues/7970 is resolved - # assert page.has_no_console_errors() - - def test_server_title_updates(self, bokeh_server_page: BokehServerPage) -> None: - slider = DateSlider(start=start, end=end, value=value, width=300) - - def modify_doc(doc): - plot = Plot(height=400, width=400, x_range=Range1d(0, 1), y_range=Range1d(0, 1), min_border=0) - - def cb(attr, old, new): - slider.title = "baz" - - slider.on_change('value', cb) - doc.add_root(column(slider, plot)) - - page = bokeh_server_page(modify_doc) - - drag_slider(page.driver, slider, 150) - - sleep(1) # noUiSlider does a transition that takes some time - - assert get_slider_title_text(page.driver, slider) > "04 Aug 2017" - - # XXX (bev) disabled until https://github.com/bokeh/bokeh/issues/7970 is resolved - # assert page.has_no_console_errors() diff --git a/tests/integration/widgets/test_datetime_range_slider.py b/tests/integration/widgets/test_datetime_range_slider.py deleted file mode 100644 index 9cb11951f29..00000000000 --- a/tests/integration/widgets/test_datetime_range_slider.py +++ /dev/null @@ -1,160 +0,0 @@ -#----------------------------------------------------------------------------- -# Copyright (c) Anaconda, Inc. All rights reserved. -# -# Powered by the Bokeh Development Team. -# -# The full license is in the file LICENSE.txt, distributed with this software. -#----------------------------------------------------------------------------- - -#----------------------------------------------------------------------------- -# Boilerplate -#----------------------------------------------------------------------------- -from __future__ import annotations # isort:skip - -import pytest ; pytest - -#----------------------------------------------------------------------------- -# Imports -#----------------------------------------------------------------------------- - -# Standard library imports -from datetime import datetime, timedelta -from time import sleep - -# Bokeh imports -from bokeh.layouts import column -from bokeh.models import ( - ColumnDataSource, - CustomJS, - DatetimeRangeSlider, - Plot, - Range1d, - Scatter, -) -from tests.support.plugins.project import BokehModelPage, BokehServerPage -from tests.support.util.selenium import ( - RECORD, - drag_range_slider, - find_elements_for, - get_slider_bar_color, - get_slider_title_text, - get_slider_title_value, -) - -#----------------------------------------------------------------------------- -# Tests -#----------------------------------------------------------------------------- - -pytest_plugins = ( - "tests.support.plugins.project", -) - -start = datetime(2022, 2, 1, 5, 4, 3) -end = datetime(2022, 3, 25, 12, 34, 56) -value = (start + timedelta(days=1), end - timedelta(days=1)) - - -@pytest.mark.selenium -class Test_DatetimeRangeSlider: - def test_display(self, bokeh_model_page: BokehModelPage) -> None: - slider = DatetimeRangeSlider(start=start, end=end, value=value, width=300) - - page = bokeh_model_page(slider) - - children = find_elements_for(page.driver, slider, "div.bk-input-group > div") - assert len(children) == 2 - - assert page.has_no_console_errors() - - def test_displays_title(self, bokeh_model_page: BokehModelPage) -> None: - slider = DatetimeRangeSlider(start=start, end=end, value=value, width=300) - - page = bokeh_model_page(slider) - - children = find_elements_for(page.driver, slider, "div.bk-input-group > div") - assert len(children) == 2 - - assert get_slider_title_text(page.driver, slider) == '02 Feb 2022 05:04:03 .. 24 Mar 2022 12:34:56' - assert get_slider_title_value(page.driver, slider) == '02 Feb 2022 05:04:03 .. 24 Mar 2022 12:34:56' - - assert page.has_no_console_errors() - - def test_title_updates(self, bokeh_model_page: BokehModelPage) -> None: - slider = DatetimeRangeSlider(start=start, end=end, value=value, width=300) - - page = bokeh_model_page(slider) - - assert get_slider_title_value(page.driver, slider) == '02 Feb 2022 05:04:03 .. 24 Mar 2022 12:34:56' - - drag_range_slider(page.driver, slider, "lower", 5) - val = get_slider_title_value(page.driver, slider).split(" .. ")[0] - assert val[:11] == "03 Feb 2022" - - drag_range_slider(page.driver, slider, "upper", -5) - val = get_slider_title_value(page.driver, slider).split(" .. ")[1] - assert val[:11] == "23 Mar 2022" - - assert page.has_no_console_errors() - - def test_displays_bar_color(self, bokeh_model_page: BokehModelPage) -> None: - slider = DatetimeRangeSlider(start=start, end=end, value=value, width=300, bar_color="red") - - page = bokeh_model_page(slider) - - children = find_elements_for(page.driver, slider, "div.bk-input-group > div") - assert len(children) == 2 - - assert get_slider_bar_color(page.driver, slider) == "rgba(255, 0, 0, 1)" - - assert page.has_no_console_errors() - - def test_server_on_change_round_trip(self, bokeh_server_page: BokehServerPage) -> None: - slider = DatetimeRangeSlider(start=start, end=end, value=value, width=300) - - def modify_doc(doc): - source = ColumnDataSource(dict(x=[1, 2], y=[1, 1], val=["a", "b"])) - plot = Plot(height=400, width=400, x_range=Range1d(0, 1), y_range=Range1d(0, 1), min_border=0) - plot.add_glyph(source, Scatter(x='x', y='y', size=20)) - plot.tags.append(CustomJS(name="custom-action", args=dict(s=source), code=RECORD("data", "s.data"))) - - def cb(attr, old, new): - source.data['val'] = [slider.value_as_datetime[0].isoformat(), slider.value_as_datetime[1].isoformat()] - - slider.on_change('value', cb) - doc.add_root(column(slider, plot)) - - page = bokeh_server_page(modify_doc) - - drag_range_slider(page.driver, slider, "lower", 5) - - page.eval_custom_action() - results = page.results - new = results['data']['val'] - assert new[0] > '2022-02-01' - - drag_range_slider(page.driver, slider, "upper", -5) - - page.eval_custom_action() - results = page.results - new = results['data']['val'] - assert new[1] < '2022-03-25' - - def test_server_bar_color_updates(self, bokeh_server_page: BokehServerPage) -> None: - slider = DatetimeRangeSlider(start=start, end=end, value=value, width=300, bar_color="red") - - def modify_doc(doc): - plot = Plot(height=400, width=400, x_range=Range1d(0, 1), y_range=Range1d(0, 1), min_border=0) - - def cb(attr, old, new): - slider.bar_color = "rgba(255, 255, 0, 1)" - - slider.on_change('value', cb) - doc.add_root(column(slider, plot)) - - page = bokeh_server_page(modify_doc) - - drag_range_slider(page.driver, slider, "lower", 150) - - sleep(1) # noUiSlider does a transition that takes some time - - assert get_slider_bar_color(page.driver, slider) == "rgba(255, 255, 0, 1)" diff --git a/tests/integration/widgets/test_div.py b/tests/integration/widgets/test_div.py deleted file mode 100644 index a96f98cf9f3..00000000000 --- a/tests/integration/widgets/test_div.py +++ /dev/null @@ -1,69 +0,0 @@ -#----------------------------------------------------------------------------- -# Copyright (c) Anaconda, Inc. All rights reserved. -# -# Powered by the Bokeh Development Team. -# -# The full license is in the file LICENSE.txt, distributed with this software. -#----------------------------------------------------------------------------- - -#----------------------------------------------------------------------------- -# Boilerplate -#----------------------------------------------------------------------------- -from __future__ import annotations # isort:skip - -import pytest ; pytest - -#----------------------------------------------------------------------------- -# Imports -#----------------------------------------------------------------------------- - -# Standard library imports -from html import escape - -# Bokeh imports -from bokeh.models import Div -from tests.support.plugins.project import BokehModelPage -from tests.support.util.selenium import find_element_for - -#----------------------------------------------------------------------------- -# Tests -#----------------------------------------------------------------------------- - -pytest_plugins = ( - "tests.support.plugins.project", -) - -text = """ -Your HTML-supported text is initialized with the text argument. The -remaining div arguments are width and height. For this example, those values -are 200 and 100 respectively.""" - - -@pytest.mark.selenium -class Test_Div: - def test_displays_div_as_html(self, bokeh_model_page: BokehModelPage) -> None: - div = Div(text=text) - page = bokeh_model_page(div) - - el = find_element_for(page.driver, div, "div") - assert el.get_attribute("innerHTML") == text - - assert page.has_no_console_errors() - - def test_displays_div_as_text(self, bokeh_model_page: BokehModelPage) -> None: - div = Div(text=text, render_as_text=True) - page = bokeh_model_page(div) - - el = find_element_for(page.driver, div, "div") - assert el.get_attribute("innerHTML") == escape(text, quote=None) - - assert page.has_no_console_errors() - - def test_set_styles(self, bokeh_model_page: BokehModelPage) -> None: - div = Div(text=text, styles={'font-size': '26px'}) - page = bokeh_model_page(div) - - el = find_element_for(page.driver, div) - assert 'font-size: 26px;' in el.get_attribute('style') - - assert page.has_no_console_errors() diff --git a/tests/integration/widgets/test_dropdown.py b/tests/integration/widgets/test_dropdown.py deleted file mode 100644 index 8b632bfe450..00000000000 --- a/tests/integration/widgets/test_dropdown.py +++ /dev/null @@ -1,156 +0,0 @@ -#----------------------------------------------------------------------------- -# Copyright (c) Anaconda, Inc. All rights reserved. -# -# Powered by the Bokeh Development Team. -# -# The full license is in the file LICENSE.txt, distributed with this software. -#----------------------------------------------------------------------------- - -#----------------------------------------------------------------------------- -# Boilerplate -#----------------------------------------------------------------------------- -from __future__ import annotations # isort:skip - -import pytest ; pytest - -#----------------------------------------------------------------------------- -# Imports -#----------------------------------------------------------------------------- - -# Bokeh imports -from bokeh.core.enums import ButtonType -from bokeh.layouts import column -from bokeh.models import ( - ColumnDataSource, - CustomJS, - Dropdown, - Plot, - Range1d, - Scatter, -) -from tests.support.plugins.project import BokehModelPage, BokehServerPage -from tests.support.util.selenium import RECORD, find_element_for - -#----------------------------------------------------------------------------- -# Tests -#----------------------------------------------------------------------------- - -pytest_plugins = ( - "tests.support.plugins.project", -) - -# XXX (bev) split dropdown (i.e. with default value) has serious problems - -items = [("Item 1", "item_1_value"), ("Item 2", "item_2_value"), ("Item 3", "item_3_value")] - - -@pytest.mark.selenium -class Test_Dropdown: - def test_displays_menu_items(self, bokeh_model_page: BokehModelPage) -> None: - button = Dropdown(label="Dropdown button", menu=items) - page = bokeh_model_page(button) - - button_el = find_element_for(page.driver, button, "button") - assert button_el.text == "Dropdown button" - button_el.click() - - menu = find_element_for(page.driver, button, ".bk-menu") - assert menu.is_displayed() - - @pytest.mark.parametrize('typ', list(ButtonType)) - def test_displays_button_type(self, typ, bokeh_model_page: BokehModelPage) -> None: - button = Dropdown(label="Dropdown button", menu=items, button_type=typ) - page = bokeh_model_page(button) - - button_el = find_element_for(page.driver, button, "button") - assert typ in button_el.get_attribute('class') - - def test_server_on_change_round_trip(self, bokeh_server_page: BokehServerPage) -> None: - button = Dropdown(label="Dropdown button", menu=items) - def modify_doc(doc): - source = ColumnDataSource(dict(x=[1, 2], y=[1, 1])) - plot = Plot(height=400, width=400, x_range=Range1d(0, 1), y_range=Range1d(0, 1), min_border=0) - plot.add_glyph(source, Scatter(x='x', y='y', size=20)) - plot.tags.append(CustomJS(name="custom-action", args=dict(s=source), code=RECORD("data", "s.data"))) - def cb(event): - item = event.item - if item == "item_1_value": - source.data = dict(x=[10, 20], y=[10, 10]) - elif item == "item_2_value": - source.data = dict(x=[100, 200], y=[100, 100]) - elif item == "item_3_value": - source.data = dict(x=[1000, 2000], y=[1000, 1000]) - button.on_event('menu_item_click', cb) - doc.add_root(column(button, plot)) - - page = bokeh_server_page(modify_doc) - - button_el = find_element_for(page.driver, button, "button") - button_el.click() - - item = find_element_for(page.driver, button, ".bk-menu > *:nth-child(1)") - item.click() - - page.eval_custom_action() - - results = page.results - assert results == {'data': {'x': [10, 20], 'y': [10, 10]}} - - button_el = find_element_for(page.driver, button, "button") - button_el.click() - - item = find_element_for(page.driver, button, ".bk-menu > *:nth-child(3)") - item.click() - - page.eval_custom_action() - - results = page.results - assert results == {'data': {'x': [1000, 2000], 'y': [1000, 1000]}} - - button_el = find_element_for(page.driver, button, "button") - button_el.click() - - item = find_element_for(page.driver, button, ".bk-menu > *:nth-child(2)") - item.click() - - page.eval_custom_action() - - results = page.results - assert results == {'data': {'x': [100, 200], 'y': [100, 100]}} - - assert page.has_no_console_errors() - - def test_js_on_change_executes(self, bokeh_model_page: BokehModelPage) -> None: - button = Dropdown(label="Dropdown button", menu=items) - button.js_on_event('menu_item_click', CustomJS(code=RECORD("value", "this.item"))) - - page = bokeh_model_page(button) - - button_el = find_element_for(page.driver, button, "button") - button_el.click() - - item = find_element_for(page.driver, button, ".bk-menu > *:nth-child(1)") - item.click() - - results = page.results - assert results == {'value': "item_1_value"} - - button_el = find_element_for(page.driver, button, "button") - button_el.click() - - item = find_element_for(page.driver, button, ".bk-menu > *:nth-child(3)") - item.click() - - results = page.results - assert results == {'value': "item_3_value"} - - button_el = find_element_for(page.driver, button, "button") - button_el.click() - - item = find_element_for(page.driver, button, ".bk-menu > *:nth-child(2)") - item.click() - - results = page.results - assert results == {'value': "item_2_value"} - - assert page.has_no_console_errors() diff --git a/tests/integration/widgets/test_multi_choice.py b/tests/integration/widgets/test_multi_choice.py deleted file mode 100644 index 03f2417eb2c..00000000000 --- a/tests/integration/widgets/test_multi_choice.py +++ /dev/null @@ -1,126 +0,0 @@ -#----------------------------------------------------------------------------- -# Copyright (c) Anaconda, Inc. All rights reserved. -# -# Powered by the Bokeh Development Team. -# -# The full license is in the file LICENSE.txt, distributed with this software. -#----------------------------------------------------------------------------- - -#----------------------------------------------------------------------------- -# Boilerplate -#----------------------------------------------------------------------------- -from __future__ import annotations # isort:skip - -import pytest ; pytest - -#----------------------------------------------------------------------------- -# Imports -#----------------------------------------------------------------------------- - -# External imports -from selenium.webdriver.common.by import By -from selenium.webdriver.common.keys import Keys - -# Bokeh imports -from bokeh.layouts import row -from bokeh.models import ( - ColumnDataSource, - CustomJS, - MultiChoice, - Plot, - Range1d, - Scatter, -) -from tests.support.plugins.project import BokehModelPage, BokehServerPage -from tests.support.util.selenium import RECORD, find_element_for - -#----------------------------------------------------------------------------- -# Tests -#----------------------------------------------------------------------------- - -pytest_plugins = ( - "tests.support.plugins.project", -) - -def mk_modify_doc(input_box: MultiChoice): - def modify_doc(doc): - source = ColumnDataSource(dict(x=[1, 2], y=[1, 1], val=["a", "b"])) - plot = Plot(height=400, width=400, x_range=Range1d(0, 1), y_range=Range1d(0, 1), min_border=0) - plot.add_glyph(source, Scatter(x='x', y='y', size=20)) - plot.tags.append(CustomJS(name="custom-action", args=dict(s=source), code=RECORD("data", "s.data"))) - input_box.title = "title" - input_box.options = ["100001", "12344556", "12344557", "3194567289", "209374209374"] - input_box.value = ["12344556", "12344557"] - def cb(attr, old, new): - source.data['val'] = [old, new] - input_box.on_change('value', cb) - doc.add_root(row(input_box, plot)) - return modify_doc - -@pytest.mark.selenium -class Test_MultiChoice: - def test_displays_multi_choice(self, bokeh_model_page: BokehModelPage) -> None: - text_input = MultiChoice(options = ["100001", "12344556", "12344557", "3194567289", "209374209374"]) - page = bokeh_model_page(text_input) - - el = find_element_for(page.driver, text_input, "input") - assert el.get_attribute('type') == "search" - - assert page.has_no_console_errors() - - def test_displays_title(self, bokeh_model_page: BokehModelPage) -> None: - text_input = MultiChoice(title="title", options = ["100001", "12344556", "12344557", "3194567289", "209374209374"]) - page = bokeh_model_page(text_input) - - el = find_element_for(page.driver, text_input, "label") - assert el.text == "title" - el = find_element_for(page.driver, text_input, "input") - assert el.get_attribute('placeholder') == "" - assert el.get_attribute('type') == "search" - - assert page.has_no_console_errors() - - def test_displays_menu(self, bokeh_model_page: BokehModelPage) -> None: - text_input = MultiChoice(title="title", options = ["100001", "12344556", "12344557", "3194567289", "209374209374"]) - page = bokeh_model_page(text_input) - - el = find_element_for(page.driver, text_input, ".choices__list--dropdown") - assert 'is-active' not in el.get_attribute('class') - - # double click to highlight and overwrite old text - inp = find_element_for(page.driver, text_input, "input") - inp.click() - assert 'is-active' in el.get_attribute('class') - - inp.send_keys(Keys.ENTER) - - selected = find_element_for(page.driver, text_input, ".choices__list--multiple") - items = selected.find_elements(By.CSS_SELECTOR, "div") - assert len(items) == 1 - - item = find_element_for(page.driver, text_input, ".choices__list--multiple div.choices__item") - assert '100001' == item.get_attribute('data-value') - - delete_button = find_element_for(page.driver, text_input, ".choices__item button") - assert "Remove item: '100001'" == delete_button.get_attribute('aria-label') - - delete_button.click() - - selected = find_element_for(page.driver, text_input, ".choices__list--multiple") - items = selected.find_elements(By.CSS_SELECTOR, "div") - assert len(items) == 0 - - assert page.has_no_console_errors() - - def test_server_on_change_round_trip_on_enter(self, bokeh_server_page: BokehServerPage) -> None: - input_box = MultiChoice() - page = bokeh_server_page(mk_modify_doc(input_box)) - - inp = find_element_for(page.driver, input_box, "input") - inp.click() - - inp.send_keys(Keys.ENTER) - page.eval_custom_action() - - results = page.results - assert results['data']['val'] == [['12344556', '12344557'], ['12344556', '12344557', '100001']] diff --git a/tests/integration/widgets/test_numeric_input.py b/tests/integration/widgets/test_numeric_input.py deleted file mode 100644 index d9d73f0dcf3..00000000000 --- a/tests/integration/widgets/test_numeric_input.py +++ /dev/null @@ -1,240 +0,0 @@ -#----------------------------------------------------------------------------- -# Copyright (c) Anaconda, Inc. All rights reserved. -# -# Powered by the Bokeh Development Team. -# -# The full license is in the file LICENSE.txt, distributed with this software. -#----------------------------------------------------------------------------- - -#----------------------------------------------------------------------------- -# Boilerplate -#----------------------------------------------------------------------------- -from __future__ import annotations # isort:skip - -import pytest ; pytest - -#----------------------------------------------------------------------------- -# Imports -#----------------------------------------------------------------------------- - -# Bokeh imports -from bokeh.application.handlers.function import ModifyDoc -from bokeh.layouts import column -from bokeh.models import ( - ColumnDataSource, - CustomJS, - NumericInput, - Plot, - Range1d, - Scatter, -) -from tests.support.plugins.project import ( - BokehModelPage, - BokehServerPage, - SinglePlotPage, -) -from tests.support.util.selenium import RECORD, enter_text_in_element, find_element_for - -#----------------------------------------------------------------------------- -# Tests -#----------------------------------------------------------------------------- - -pytest_plugins = ( - "tests.support.plugins.project", -) - -def mk_modify_doc(num_input: NumericInput) -> tuple[ModifyDoc, Plot]: - plot = Plot(height=400, width=400, x_range=Range1d(0, 1), y_range=Range1d(0, 1), min_border=0) - def modify_doc(doc): - source = ColumnDataSource(dict(x=[1, 2], y=[1, 1], val=["a", "b"])) - - plot.add_glyph(source, Scatter(x='x', y='y')) - plot.tags.append(CustomJS(name="custom-action", args=dict(s=source), code=RECORD("data", "s.data"))) - - def cb(attr, old, new): - source.data['val'] = [old, new] - - num_input.on_change('value', cb) - doc.add_root(column(num_input, plot)) - return doc - return modify_doc, plot - -@pytest.mark.selenium -class Test_NumericInput: - - def test_display_number_input(self, bokeh_model_page: BokehModelPage) -> None: - num_input = NumericInput() - page = bokeh_model_page(num_input) - - el = find_element_for(page.driver, num_input, "input") - assert el.get_attribute('type') == "text" - - assert page.has_no_console_errors() - - def test_displays_title(self, bokeh_model_page: BokehModelPage) -> None: - num_input = NumericInput(title="title") - page = bokeh_model_page(num_input) - - el = find_element_for(page.driver, num_input, "label") - assert el.text == "title" - el = find_element_for(page.driver, num_input, "input") - assert el.get_attribute('type') == "text" - - assert page.has_no_console_errors() - - def test_displays_placeholder(self, bokeh_model_page: BokehModelPage) -> None: - num_input = NumericInput(placeholder="placeholder") - page = bokeh_model_page(num_input) - - el = find_element_for(page.driver, num_input, "label") - assert el.text == "" - el = find_element_for(page.driver, num_input, "input") - assert el.get_attribute('placeholder') == "placeholder" - assert el.get_attribute('type') == "text" - - def test_server_on_change_no_round_trip_without_enter_or_click(self, bokeh_server_page: BokehServerPage) -> None: - num_input = NumericInput(low=-1, high=100, value=4) - modify_doc, _ = mk_modify_doc(num_input) - page = bokeh_server_page(modify_doc) - - el = find_element_for(page.driver, num_input, "input") - enter_text_in_element(page.driver, el, "pre", enter=False) # not change event if enter is not pressed - - page.eval_custom_action() - - results = page.results - assert results['data']['val'] == ["a", "b"] - - assert page.has_no_console_errors() - - def test_server_on_change_round_trip(self, bokeh_server_page: BokehServerPage) -> None: - num_input = NumericInput(low=-1, high=100, value=4) - modify_doc, plot = mk_modify_doc(num_input) - page = bokeh_server_page(modify_doc) - - el = find_element_for(page.driver, num_input, "input") - enter_text_in_element(page.driver, el, "2") - - page.eval_custom_action() - - results = page.results - assert results['data']['val'] == [4, 42] - - # double click to highlight and overwrite old text - enter_text_in_element(page.driver, el, "34", click=2) - - page.eval_custom_action() - - results = page.results - assert results['data']['val'] == [42, 34] - - # Check clicking outside input also triggers - enter_text_in_element(page.driver, el, "56", click=2, enter=False) - page.click_canvas_at_position(plot, 10, 10) - - page.eval_custom_action() - - results = page.results - assert results['data']['val'] == [34, 56] - - assert page.has_no_console_errors() - - def test_js_on_change_executes(self, single_plot_page: SinglePlotPage) -> None: - source = ColumnDataSource(dict(x=[1, 2], y=[1, 1])) - plot = Plot(height=400, width=400, x_range=Range1d(0, 1), y_range=Range1d(0, 1), min_border=0) - plot.add_glyph(source, Scatter(x='x', y='y', size=20)) - num_input = NumericInput() - num_input.js_on_change('value', CustomJS(code=RECORD("value", "cb_obj.value"))) - - page = single_plot_page(column(num_input, plot)) - - el = find_element_for(page.driver, num_input, "input") - enter_text_in_element(page.driver, el, "10") - - results = page.results - assert results['value'] == 10 - - # double click to highlight and overwrite old text - enter_text_in_element(page.driver, el, "20", click=2) - - results = page.results - assert results['value'] == 20 - - # Check clicking outside input also triggers - enter_text_in_element(page.driver, el, "30", click=2, enter=False) - page.click_canvas_at_position(plot, 10, 10) - results = page.results - - assert results['value'] == 30 - - assert page.has_no_console_errors() - - def test_low_high(self, single_plot_page: SinglePlotPage) -> None: - source = ColumnDataSource(dict(x=[1, 2], y=[1, 1])) - plot = Plot(height=400, width=400, x_range=Range1d(0, 1), y_range=Range1d(0, 1), min_border=0) - plot.add_glyph(source, Scatter(x='x', y='y', size=20)) - num_input = NumericInput(value=4, low=-1, high=10) - num_input.js_on_change('value', CustomJS(code=RECORD("value", "cb_obj.value"))) - - page = single_plot_page(column(num_input, plot)) - - el = find_element_for(page.driver, num_input, "input") - assert el.get_attribute('value') == "4" - - enter_text_in_element(page.driver, el, "30", click=2) - assert el.get_attribute('value') == "10" - - enter_text_in_element(page.driver, el, "-10", click=2) - assert el.get_attribute('value') == "-1" - - def test_int_inputs(self, single_plot_page: SinglePlotPage) -> None: - - values_to_enter = ["0", "1", "-1", "+5", - "0.1", "-0.1", "+0.1", "-.1", "+.1", - "1e-6", "1.e5", "-1e+3", "-1.e-5", - "a"] - - expected_results = [0, 1, -1, 5, 1, -1, 1, -1, 1, - 10, 10, -13, -15, None] - - source = ColumnDataSource(dict(x=[1, 2], y=[1, 1])) - plot = Plot(height=400, width=400, x_range=Range1d(0, 1), y_range=Range1d(0, 1), min_border=0) - plot.add_glyph(source, Scatter(x='x', y='y', size=20)) - num_input = NumericInput(high=10) - num_input.js_on_change('value', CustomJS(code=RECORD("value", "cb_obj.value"))) - - page = single_plot_page(column(num_input, plot)) - el = find_element_for(page.driver, num_input, "input") - - for val, res in zip(values_to_enter, expected_results): - el.clear() - enter_text_in_element(page.driver, el, val) - - results = page.results - assert results['value'] == res - - def test_float_inputs(self, single_plot_page: SinglePlotPage) -> None: - - values_to_enter = ["0", "1", "-1", "+5", - "0.1", "-0.1", "+0.1", "-.1", "+.1", - "1e-6", "1.e5", "-1e+3", "-1.e-5", - "a"] - - expected_results = [0, 1, -1, 5, 0.1, -0.1, 0.1, -0.1, 0.1, - 1e-6, 10, -1e3, -1e-5, None] - - source = ColumnDataSource(dict(x=[1, 2], y=[1, 1])) - plot = Plot(height=400, width=400, x_range=Range1d(0, 1), y_range=Range1d(0, 1), min_border=0) - plot.add_glyph(source, Scatter(x='x', y='y', size=20)) - num_input = NumericInput(high=10, mode="float") - num_input.js_on_change('value', CustomJS(code=RECORD("value", "cb_obj.value"))) - - page = single_plot_page(column(num_input, plot)) - el = find_element_for(page.driver, num_input, "input") - - for val, res in zip(values_to_enter, expected_results): - el.clear() - enter_text_in_element(page.driver, el, val) - - results = page.results - assert results['value'] == res diff --git a/tests/integration/widgets/test_paragraph.py b/tests/integration/widgets/test_paragraph.py deleted file mode 100644 index b4bba686301..00000000000 --- a/tests/integration/widgets/test_paragraph.py +++ /dev/null @@ -1,62 +0,0 @@ -#----------------------------------------------------------------------------- -# Copyright (c) Anaconda, Inc. All rights reserved. -# -# Powered by the Bokeh Development Team. -# -# The full license is in the file LICENSE.txt, distributed with this software. -#----------------------------------------------------------------------------- - -#----------------------------------------------------------------------------- -# Boilerplate -#----------------------------------------------------------------------------- -from __future__ import annotations # isort:skip - -import pytest ; pytest - -#----------------------------------------------------------------------------- -# Imports -#----------------------------------------------------------------------------- - -# Standard library imports -from html import escape - -# Bokeh imports -from bokeh.models import Paragraph -from tests.support.plugins.project import BokehModelPage -from tests.support.util.selenium import find_element_for - -#----------------------------------------------------------------------------- -# Tests -#----------------------------------------------------------------------------- - -pytest_plugins = ( - "tests.support.plugins.project", -) - -text = """ -Your HTML-supported text is initialized with the text argument. The -remaining div arguments are width and height. For this example, those values -are 200 and 100 respectively.""" - - -@pytest.mark.selenium -class Test_TextParagraph: - def test_displays_div_as_text(self, bokeh_model_page: BokehModelPage) -> None: - para = Paragraph(text=text) - - page = bokeh_model_page(para) - - el = find_element_for(page.driver, para, "div p") - assert el.get_attribute("innerHTML") == escape(text, quote=None) - - assert page.has_no_console_errors() - - def test_set_styles(self, bokeh_model_page: BokehModelPage) -> None: - para = Paragraph(text=text, styles={'font-size': '26px'}) - - page = bokeh_model_page(para) - - el = find_element_for(page.driver, para) - assert 'font-size: 26px;' in el.get_attribute('style') - - assert page.has_no_console_errors() diff --git a/tests/integration/widgets/test_password_input.py b/tests/integration/widgets/test_password_input.py deleted file mode 100644 index d42fd3255aa..00000000000 --- a/tests/integration/widgets/test_password_input.py +++ /dev/null @@ -1,168 +0,0 @@ -#----------------------------------------------------------------------------- -# Copyright (c) Anaconda, Inc. All rights reserved. -# -# Powered by the Bokeh Development Team. -# -# The full license is in the file LICENSE.txt, distributed with this software. -#----------------------------------------------------------------------------- - -#----------------------------------------------------------------------------- -# Boilerplate -#----------------------------------------------------------------------------- -from __future__ import annotations # isort:skip - -import pytest ; pytest - -#----------------------------------------------------------------------------- -# Imports -#----------------------------------------------------------------------------- - -# Bokeh imports -from bokeh.application.handlers.function import ModifyDoc -from bokeh.layouts import column -from bokeh.models import ( - ColumnDataSource, - CustomJS, - PasswordInput, - Plot, - Range1d, - Scatter, -) -from tests.support.plugins.project import ( - BokehModelPage, - BokehServerPage, - SinglePlotPage, -) -from tests.support.util.selenium import RECORD, enter_text_in_element, find_element_for - -#----------------------------------------------------------------------------- -# Tests -#----------------------------------------------------------------------------- - -pytest_plugins = ( - "tests.support.plugins.project", -) - -def mk_modify_doc(text_input: PasswordInput) -> tuple[ModifyDoc, Plot]: - plot = Plot(height=400, width=400, x_range=Range1d(0, 1), y_range=Range1d(0, 1), min_border=0) - def modify_doc(doc): - source = ColumnDataSource(dict(x=[1, 2], y=[1, 1], val=["a", "b"])) - plot.add_glyph(source, Scatter(x='x', y='y', size=20)) - plot.tags.append(CustomJS(name="custom-action", args=dict(s=source), code=RECORD("data", "s.data"))) - def cb(attr, old, new): - source.data['val'] = [old, new] - text_input.on_change('value', cb) - doc.add_root(column(text_input, plot)) - return modify_doc, plot - -@pytest.mark.selenium -class Test_PasswordInput: - def test_displays_password_input(self, bokeh_model_page: BokehModelPage) -> None: - pw_input = PasswordInput() - page = bokeh_model_page(pw_input) - - el = find_element_for(page.driver, pw_input, "input") - assert el.get_attribute('type') == "password" - - assert page.has_no_console_errors() - - def test_displays_title(self, bokeh_model_page: BokehModelPage) -> None: - pw_input = PasswordInput(title="title") - page = bokeh_model_page(pw_input) - - el = find_element_for(page.driver, pw_input, "label") - assert el.text == "title" - el = find_element_for(page.driver, pw_input, "input") - assert el.get_attribute('placeholder') == "" - assert el.get_attribute('type') == "password" - - assert page.has_no_console_errors() - - def test_displays_placeholder(self, bokeh_model_page: BokehModelPage) -> None: - pw_input = PasswordInput(placeholder="placeholder") - page = bokeh_model_page(pw_input) - - el = find_element_for(page.driver, pw_input, "label") - assert el.text == "" - el = find_element_for(page.driver, pw_input, "input") - assert el.get_attribute('placeholder') == "placeholder" - assert el.get_attribute('type') == "password" - - assert page.has_no_console_errors() - - def test_server_on_change_no_round_trip_without_enter_or_click(self, bokeh_server_page: BokehServerPage) -> None: - text_input = PasswordInput() - modify_doc, _ = mk_modify_doc(text_input) - page = bokeh_server_page(modify_doc) - - el = find_element_for(page.driver, text_input, "input") - enter_text_in_element(page.driver, el, "pre", enter=False) # not change event if enter is not pressed - - page.eval_custom_action() - - results = page.results - assert results['data']['val'] == ["a", "b"] - - assert page.has_no_console_errors() - - def test_server_on_change_round_trip(self, bokeh_server_page: BokehServerPage) -> None: - text_input = PasswordInput() - modify_doc, plot = mk_modify_doc(text_input) - page = bokeh_server_page(modify_doc) - - el = find_element_for(page.driver, text_input, "input") - enter_text_in_element(page.driver, el, "val1") - - page.eval_custom_action() - - results = page.results - assert results['data']['val'] == ["", "val1"] - - # double click to highlight and overwrite old text - enter_text_in_element(page.driver, el, "val2", click=2) - - page.eval_custom_action() - - results = page.results - assert results['data']['val'] == ["val1", "val2"] - - # Check clicking outside input also triggers - enter_text_in_element(page.driver, el, "val3", click=2, enter=False) - page.click_canvas_at_position(plot, 10, 10) - - page.eval_custom_action() - - results = page.results - assert results['data']['val'] == ["val2", "val3"] - - assert page.has_no_console_errors() - - def test_js_on_change_executes(self, single_plot_page: SinglePlotPage) -> None: - source = ColumnDataSource(dict(x=[1, 2], y=[1, 1])) - plot = Plot(height=400, width=400, x_range=Range1d(0, 1), y_range=Range1d(0, 1), min_border=0) - plot.add_glyph(source, Scatter(x='x', y='y', size=20)) - text_input = PasswordInput() - text_input.js_on_change('value', CustomJS(code=RECORD("value", "cb_obj.value"))) - - page = single_plot_page(column(text_input, plot)) - - el = find_element_for(page.driver, text_input, "input") - enter_text_in_element(page.driver, el, "val1") - - results = page.results - assert results['value'] == 'val1' - - # double click to highlight and overwrite old text - enter_text_in_element(page.driver, el, "val2", click=2) - - results = page.results - assert results['value'] == 'val2' - - # Check clicking outside input also triggers - enter_text_in_element(page.driver, el, "val3", click=2, enter=False) - page.click_canvas_at_position(plot, 10, 10) - results = page.results - - assert results['value'] == 'val3' - - assert page.has_no_console_errors() diff --git a/tests/integration/widgets/test_pretext.py b/tests/integration/widgets/test_pretext.py deleted file mode 100644 index 5ddf2c246a2..00000000000 --- a/tests/integration/widgets/test_pretext.py +++ /dev/null @@ -1,60 +0,0 @@ -#----------------------------------------------------------------------------- -# Copyright (c) Anaconda, Inc. All rights reserved. -# -# Powered by the Bokeh Development Team. -# -# The full license is in the file LICENSE.txt, distributed with this software. -#----------------------------------------------------------------------------- - -#----------------------------------------------------------------------------- -# Boilerplate -#----------------------------------------------------------------------------- -from __future__ import annotations # isort:skip - -import pytest ; pytest - -#----------------------------------------------------------------------------- -# Imports -#----------------------------------------------------------------------------- - -# Standard library imports -from html import escape - -# Bokeh imports -from bokeh.models import PreText -from tests.support.plugins.project import BokehModelPage -from tests.support.util.selenium import find_element_for - -#----------------------------------------------------------------------------- -# Tests -#----------------------------------------------------------------------------- - -pytest_plugins = ( - "tests.support.plugins.project", -) - -text = """ -Your HTML-supported text is initialized with the text argument. The -remaining div arguments are width and height. For this example, those values -are 200 and 100 respectively.""" - - -@pytest.mark.selenium -class Test_PreText: - def test_displays_div_as_text(self, bokeh_model_page: BokehModelPage) -> None: - para = PreText(text=text) - page = bokeh_model_page(para) - - el = find_element_for(page.driver, para, "div pre") - assert el.get_attribute("innerHTML") == escape(text, quote=None) - - assert page.has_no_console_errors() - - def test_set_styles(self, bokeh_model_page: BokehModelPage) -> None: - para = PreText(text=text, styles={'font-size': '26px'}) - page = bokeh_model_page(para) - - el = find_element_for(page.driver, para) - assert 'font-size: 26px;' in el.get_attribute('style') - - assert page.has_no_console_errors() diff --git a/tests/integration/widgets/test_radio_button_group.py b/tests/integration/widgets/test_radio_button_group.py deleted file mode 100644 index 6419a1fc9a5..00000000000 --- a/tests/integration/widgets/test_radio_button_group.py +++ /dev/null @@ -1,96 +0,0 @@ -#----------------------------------------------------------------------------- -# Copyright (c) Anaconda, Inc. All rights reserved. -# -# Powered by the Bokeh Development Team. -# -# The full license is in the file LICENSE.txt, distributed with this software. -#----------------------------------------------------------------------------- - -#----------------------------------------------------------------------------- -# Boilerplate -#----------------------------------------------------------------------------- -from __future__ import annotations # isort:skip - -import pytest ; pytest - -#----------------------------------------------------------------------------- -# Imports -#----------------------------------------------------------------------------- - -# Bokeh imports -from bokeh.layouts import column -from bokeh.models import ( - ColumnDataSource, - CustomJS, - Plot, - RadioButtonGroup, - Range1d, - Scatter, -) -from tests.support.plugins.project import BokehModelPage, BokehServerPage -from tests.support.util.selenium import RECORD, find_element_for - -#----------------------------------------------------------------------------- -# Tests -#----------------------------------------------------------------------------- - -pytest_plugins = ( - "tests.support.plugins.project", -) - -LABELS = ["Option 1", "Option 2", "Option 3"] - - -@pytest.mark.selenium -class Test_RadioButtonGroup: - def test_server_on_change_round_trip(self, bokeh_server_page: BokehServerPage) -> None: - group = RadioButtonGroup(labels=LABELS) - def modify_doc(doc): - source = ColumnDataSource(dict(x=[1, 2], y=[1, 1], val=["a", "b"])) - plot = Plot(height=400, width=400, x_range=Range1d(0, 1), y_range=Range1d(0, 1), min_border=0) - plot.add_glyph(source, Scatter(x='x', y='y', size=20)) - plot.tags.append(CustomJS(name="custom-action", args=dict(s=source), code=RECORD("data", "s.data"))) - def cb(event): - source.data['val'] = [group.active, "b"] - group.on_event('button_click', cb) - doc.add_root(column(group, plot)) - - page = bokeh_server_page(modify_doc) - - el = find_element_for(page.driver, group, ".bk-btn:nth-child(3)") - el.click() - - page.eval_custom_action() - - results = page.results - assert results['data']['val'] == [2, "b"] - - el = find_element_for(page.driver, group, ".bk-btn:nth-child(1)") - el.click() - - page.eval_custom_action() - - results = page.results - assert results['data']['val'] == [0, "b"] - - assert page.has_no_console_errors() - - def test_js_on_change_executes(self, bokeh_model_page: BokehModelPage) -> None: - group = RadioButtonGroup(labels=LABELS) - group.js_on_event('button_click', CustomJS(code=RECORD("active", "cb_obj.origin.active"))) - - page = bokeh_model_page(group) - - el = find_element_for(page.driver, group, ".bk-btn:nth-child(3)") - el.click() - - results = page.results - assert results['active'] == 2 - - el = find_element_for(page.driver, group, ".bk-btn:nth-child(1)") - el.click() - - results = page.results - assert results['active'] == 0 - - assert page.has_no_console_errors() diff --git a/tests/integration/widgets/test_radio_group.py b/tests/integration/widgets/test_radio_group.py deleted file mode 100644 index acbfaaa06d3..00000000000 --- a/tests/integration/widgets/test_radio_group.py +++ /dev/null @@ -1,113 +0,0 @@ -#----------------------------------------------------------------------------- -# Copyright (c) Anaconda, Inc. All rights reserved. -# -# Powered by the Bokeh Development Team. -# -# The full license is in the file LICENSE.txt, distributed with this software. -#----------------------------------------------------------------------------- - -#----------------------------------------------------------------------------- -# Boilerplate -#----------------------------------------------------------------------------- -from __future__ import annotations # isort:skip - -import pytest ; pytest - -#----------------------------------------------------------------------------- -# Imports -#----------------------------------------------------------------------------- - -# External imports -from selenium.webdriver.common.by import By - -# Bokeh imports -from bokeh.layouts import column -from bokeh.models import ( - ColumnDataSource, - CustomJS, - Plot, - RadioGroup, - Range1d, - Scatter, -) -from tests.support.plugins.project import BokehModelPage, BokehServerPage -from tests.support.util.selenium import RECORD, find_element_for, find_elements_for - -#----------------------------------------------------------------------------- -# Tests -#----------------------------------------------------------------------------- - -pytest_plugins = ( - "tests.support.plugins.project", -) - -LABELS = ["Option 1", "Option 2", "Option 3"] - - -@pytest.mark.selenium -class Test_RadioGroup: - @pytest.mark.parametrize('inline', [True, False]) - def test_displays_options_list_of_string_labels_setting_inline(self, inline, bokeh_model_page: BokehModelPage) -> None: - group = RadioGroup(labels=LABELS, inline=inline) - page = bokeh_model_page(group) - - labels = find_elements_for(page.driver, group, "label") - assert len(labels) == 3 - - for i, label in enumerate(labels): - assert label.text == LABELS[i] - input = label.find_element(By.TAG_NAME, 'input') - assert input.get_attribute('value') == str(i) - assert input.get_attribute('type') == 'radio' - - def test_server_on_change_round_trip(self, bokeh_server_page: BokehServerPage) -> None: - group = RadioGroup(labels=LABELS) - def modify_doc(doc): - source = ColumnDataSource(dict(x=[1, 2], y=[1, 1], val=["a", "b"])) - plot = Plot(height=400, width=400, x_range=Range1d(0, 1), y_range=Range1d(0, 1), min_border=0) - plot.add_glyph(source, Scatter(x='x', y='y', size=20)) - plot.tags.append(CustomJS(name="custom-action", args=dict(s=source), code=RECORD("data", "s.data"))) - def cb(attr, old, new): - source.data['val'] = [new, "b"] - group.on_change('active', cb) - doc.add_root(column(group, plot)) - - page = bokeh_server_page(modify_doc) - - el = find_element_for(page.driver, group, 'input[value="2"]') - el.click() - - page.eval_custom_action() - - results = page.results - assert results['data']['val'] == [2, "b"] - - el = find_element_for(page.driver, group, 'input[value="0"]') - el.click() - - page.eval_custom_action() - - results = page.results - assert results['data']['val'] == [0, "b"] - - assert page.has_no_console_errors() - - def test_js_on_change_executes(self, bokeh_model_page: BokehModelPage) -> None: - group = RadioGroup(labels=LABELS) - group.js_on_change('active', CustomJS(code=RECORD("active", "cb_obj.active"))) - - page = bokeh_model_page(group) - - el = find_element_for(page.driver, group, 'input[value="2"]') - el.click() - - results = page.results - assert results['active'] == 2 - - el = find_element_for(page.driver, group, 'input[value="0"]') - el.click() - - results = page.results - assert results['active'] == 0 - - assert page.has_no_console_errors() diff --git a/tests/integration/widgets/test_range_slider.py b/tests/integration/widgets/test_range_slider.py deleted file mode 100644 index b0619515f84..00000000000 --- a/tests/integration/widgets/test_range_slider.py +++ /dev/null @@ -1,244 +0,0 @@ -#----------------------------------------------------------------------------- -# Copyright (c) Anaconda, Inc. All rights reserved. -# -# Powered by the Bokeh Development Team. -# -# The full license is in the file LICENSE.txt, distributed with this software. -#----------------------------------------------------------------------------- - -#----------------------------------------------------------------------------- -# Boilerplate -#----------------------------------------------------------------------------- -from __future__ import annotations # isort:skip - -import pytest ; pytest - -#----------------------------------------------------------------------------- -# Imports -#----------------------------------------------------------------------------- - -# Standard library imports -from time import sleep - -# Bokeh imports -from bokeh.layouts import column -from bokeh.models import ( - ColumnDataSource, - CustomJS, - Plot, - Range1d, - RangeSlider, - Scatter, -) -from bokeh.models.formatters import BasicTickFormatter -from tests.support.plugins.project import BokehModelPage, BokehServerPage -from tests.support.util.selenium import ( - RECORD, - Keys, - drag_range_slider, - find_element_for, - find_elements_for, - get_slider_bar_color, - get_slider_title_text, - get_slider_title_value, - select_element_and_press_key, -) - -#----------------------------------------------------------------------------- -# Tests -#----------------------------------------------------------------------------- - -pytest_plugins = ( - "tests.support.plugins.project", -) - - -@pytest.mark.selenium -class Test_RangeSlider: - def test_display(self, bokeh_model_page: BokehModelPage) -> None: - slider = RangeSlider(start=0, end=10, value=(1, 5), width=300) - page = bokeh_model_page(slider) - - children = find_elements_for(page.driver, slider, "div.bk-input-group > div") - assert len(children) == 2 - - assert page.has_no_console_errors() - - def test_displays_title(self, bokeh_model_page: BokehModelPage) -> None: - slider = RangeSlider(start=0, end=10, value=(1, 5), title="bar", width=300) - page = bokeh_model_page(slider) - - children = find_elements_for(page.driver, slider, "div.bk-input-group > div") - assert len(children) == 2 - - assert get_slider_title_text(page.driver, slider) == "bar: 1 .. 5" - assert get_slider_title_value(page.driver, slider) == "1 .. 5" - - assert page.has_no_console_errors() - - def test_displays_title_scientific(self, bokeh_model_page: BokehModelPage) -> None: - slider = RangeSlider(start=0, end=10e-6, step=1e-6, value=(1e-6, 8e-6), title="bar", - format=BasicTickFormatter(precision=2), width=300) - page = bokeh_model_page(slider) - - children = find_elements_for(page.driver, slider, "div.bk-input-group > div") - assert len(children) == 2 - - t0 = get_slider_title_text(page.driver, slider) - t1 = get_slider_title_value(page.driver, slider) - - assert t0 == "bar: 1.00e\u22126 .. 8.00e\u22126" - assert t1 == "1.00e\u22126 .. 8.00e\u22126" - - assert page.has_no_console_errors() - - def test_title_updates(self, bokeh_model_page: BokehModelPage) -> None: - slider = RangeSlider(start=0, end=10, value=(1, 9), title="bar", width=300) - - page = bokeh_model_page(slider) - - assert get_slider_title_value(page.driver, slider) == "1 .. 9" - - drag_range_slider(page.driver, slider, "lower", 50) - value = get_slider_title_value(page.driver, slider).split()[0] - assert float(value) > 1 - assert float(value) == int(value) # integral step size - - # don't go past upper handle - drag_range_slider(page.driver, slider, "lower", 50) - value = get_slider_title_value(page.driver, slider).split()[0] - assert float(value) > 2 - - drag_range_slider(page.driver, slider, "lower", -135) - value = get_slider_title_value(page.driver, slider).split()[0] - assert float(value) == 0 - - assert page.has_no_console_errors() - - def test_keypress_event(self, bokeh_model_page: BokehModelPage) -> None: - slider = RangeSlider(start=0, end=10, value=(1, 5), title="bar", width=300) - page = bokeh_model_page(slider) - - handle_lower = find_element_for(page.driver, slider, ".noUi-handle-lower") - handle_upper = find_element_for(page.driver, slider, ".noUi-handle-upper") - - select_element_and_press_key(page.driver, handle_lower, Keys.ARROW_RIGHT, press_number=1) - assert get_slider_title_value(page.driver, slider) == "2 .. 5" - select_element_and_press_key(page.driver, handle_lower, Keys.ARROW_LEFT, press_number=5) - assert get_slider_title_value(page.driver, slider) == "0 .. 5" - select_element_and_press_key(page.driver, handle_lower, Keys.ARROW_RIGHT, press_number=11) - assert get_slider_title_value(page.driver, slider) == "5 .. 5" - select_element_and_press_key(page.driver, handle_upper, Keys.ARROW_RIGHT, press_number=1) - assert get_slider_title_value(page.driver, slider) == "5 .. 6" - select_element_and_press_key(page.driver, handle_upper, Keys.ARROW_LEFT, press_number=2) - assert get_slider_title_value(page.driver, slider) == "5 .. 5" - select_element_and_press_key(page.driver, handle_upper, Keys.ARROW_RIGHT, press_number=6) - assert get_slider_title_value(page.driver, slider) == "5 .. 10" - - assert page.has_no_console_errors() - - def test_displays_bar_color(self, bokeh_model_page: BokehModelPage) -> None: - slider = RangeSlider(start=0, end=10, value=(1, 5), title="bar", width=300, bar_color="red") - page = bokeh_model_page(slider) - - children = find_elements_for(page.driver, slider, "div.bk-input-group > div") - assert len(children) == 2 - - assert get_slider_bar_color(page.driver, slider) == "rgba(255, 0, 0, 1)" - - assert page.has_no_console_errors() - - def test_js_on_change_executes(self, bokeh_model_page: BokehModelPage) -> None: - slider = RangeSlider(start=0, end=10, value=(1, 5), title="bar", width=300) - slider.js_on_change('value', CustomJS(code=RECORD("value", "cb_obj.value"))) - - page = bokeh_model_page(slider) - - drag_range_slider(page.driver, slider, "lower", 150) - - results = page.results - assert float(results['value'][0]) > 1 - assert float(results['value'][1]) == 5 - - drag_range_slider(page.driver, slider, "lower", 150) - - results = page.results - assert float(results['value'][0]) > 1 - assert float(results['value'][1]) > 5 - - assert page.has_no_console_errors() - - - def test_server_on_change_round_trip(self, bokeh_server_page: BokehServerPage) -> None: - slider = RangeSlider(start=0, end=10, value=(1, 9), title="bar", width=300) - - def modify_doc(doc): - source = ColumnDataSource(dict(x=[1, 2], y=[1, 1], val=["a", "b"])) - plot = Plot(height=400, width=400, x_range=Range1d(0, 1), y_range=Range1d(0, 1), min_border=0) - plot.add_glyph(source, Scatter(x='x', y='y', size=20)) - plot.tags.append(CustomJS(name="custom-action", args=dict(s=source), code=RECORD("data", "s.data"))) - - def cb(attr, old, new): - source.data['val'] = [old, new] - - slider.on_change('value', cb) - doc.add_root(column(slider, plot)) - - page = bokeh_server_page(modify_doc) - - drag_range_slider(page.driver, slider, "lower", 50) - - page.eval_custom_action() - results = page.results - old, new = results['data']['val'] - assert float(old[0]) == 1 - assert float(new[0]) > 1 - - drag_range_slider(page.driver, slider, "lower", 50) - - page.eval_custom_action() - results = page.results - old, new = results['data']['val'] - assert float(new[0]) > 2 - - drag_range_slider(page.driver, slider, "lower", -135) - - page.eval_custom_action() - results = page.results - old, new = results['data']['val'] - assert float(new[0]) == 0 - - # XXX (bev) skip keypress part of test until it can be fixed - # handle = find_element_for(page.driver, slider, ".noUi-handle-lower") - # select_element_and_press_key(page.driver, handle, Keys.ARROW_RIGHT) - - # page.eval_custom_action() - # results = page.results - # old, new = results['data']['val'] - # assert float(new[0]) >= 1 - - # XXX (bev) disabled until https://github.com/bokeh/bokeh/issues/7970 is resolved - # assert page.has_no_console_errors() - - def test_server_bar_color_updates(self, bokeh_server_page: BokehServerPage) -> None: - slider = RangeSlider(start=0, end=10, value=(1, 5), title="bar", width=300) - - def modify_doc(doc): - plot = Plot(height=400, width=400, x_range=Range1d(0, 1), y_range=Range1d(0, 1), min_border=0) - - def cb(attr, old, new): - slider.bar_color = "rgba(255, 255, 0, 1)" - - slider.on_change('value', cb) - doc.add_root(column(slider, plot)) - - page = bokeh_server_page(modify_doc) - - drag_range_slider(page.driver, slider, "lower", 150) - - sleep(1) # noUiSlider does a transition that takes some time - - assert get_slider_bar_color(page.driver, slider) == "rgba(255, 255, 0, 1)" - - # XXX (bev) disabled until https://github.com/bokeh/bokeh/issues/7970 is resolved - # assert page.has_no_console_errors() diff --git a/tests/integration/widgets/test_select.py b/tests/integration/widgets/test_select.py deleted file mode 100644 index ae42480a206..00000000000 --- a/tests/integration/widgets/test_select.py +++ /dev/null @@ -1,274 +0,0 @@ -#----------------------------------------------------------------------------- -# Copyright (c) Anaconda, Inc. All rights reserved. -# -# Powered by the Bokeh Development Team. -# -# The full license is in the file LICENSE.txt, distributed with this software. -#----------------------------------------------------------------------------- - -#----------------------------------------------------------------------------- -# Boilerplate -#----------------------------------------------------------------------------- -from __future__ import annotations # isort:skip - -import pytest ; pytest - -#----------------------------------------------------------------------------- -# Imports -#----------------------------------------------------------------------------- - -# External imports -from selenium.webdriver.common.by import By - -# Bokeh imports -from bokeh.layouts import column -from bokeh.models import ( - ColumnDataSource, - CustomJS, - Plot, - Range1d, - Scatter, - Select, -) -from tests.support.plugins.project import BokehModelPage, BokehServerPage -from tests.support.util.selenium import RECORD, find_element_for - -#----------------------------------------------------------------------------- -# Tests -#----------------------------------------------------------------------------- - -pytest_plugins = ( - "tests.support.plugins.project", -) - -@pytest.mark.selenium -class Test_Select: - def test_displays_title(self, bokeh_model_page: BokehModelPage) -> None: - select = Select(options=["Option 1", "Option 2", "Option 3"], title="title") - - page = bokeh_model_page(select) - - el = find_element_for(page.driver, select, "label") - assert el.text == "title" - - assert page.has_no_console_errors() - - def test_displays_options_list_of_string_options(self, bokeh_model_page: BokehModelPage) -> None: - select = Select(options=["Option 1", "Option 2", "Option 3"]) - - page = bokeh_model_page(select) - - el = find_element_for(page.driver, select, "label") - assert el.text == "" - - el = find_element_for(page.driver, select, "select") - opts = el.find_elements(By.TAG_NAME, 'option') - assert len(opts) == 3 - - for i, opt in enumerate(opts, 1): - assert opt.text == f"Option {i}" - assert opt.get_attribute('value') == f"Option {i}" - - assert page.has_no_console_errors() - - def test_displays_options_list_of_string_options_with_default_value(self, bokeh_model_page: BokehModelPage) -> None: - select = Select(options=["Option 1", "Option 2", "Option 3"], value="Option 3") - - page = bokeh_model_page(select) - - el = find_element_for(page.driver, select, "label") - assert el.text == "" - - el = find_element_for(page.driver, select, "select") - opts = el.find_elements(By.TAG_NAME, 'option') - assert len(opts) == 3 - - for i, opt in enumerate(opts, 1): - assert opt.text == f"Option {i}" - assert opt.get_attribute('value') == f"Option {i}" - - assert page.has_no_console_errors() - - - def test_displays_list_of_tuple_options(self, bokeh_model_page: BokehModelPage) -> None: - select = Select(options=[("1", "Option 1"), ("2", "Option 2"), ("3", "Option 3")]) - - page = bokeh_model_page(select) - - el = find_element_for(page.driver, select, "label") - assert el.text == "" - - el = find_element_for(page.driver, select, "select") - opts = el.find_elements(By.TAG_NAME, 'option') - assert len(opts) == 3 - - for i, opt in enumerate(opts, 1): - assert opt.text == f"Option {i}" - assert opt.get_attribute('value') == str(i) - - assert page.has_no_console_errors() - - def test_displays_list_of_tuple_options_with_default_value(self, bokeh_model_page: BokehModelPage) -> None: - select = Select(options=[("1", "Option 1"), ("2", "Option 2"), ("3", "Option 3")], value="3") - - page = bokeh_model_page(select) - - el = find_element_for(page.driver, select, "label") - assert el.text == "" - - el = find_element_for(page.driver, select, "select") - opts = el.find_elements(By.TAG_NAME, 'option') - assert len(opts) == 3 - - for i, opt in enumerate(opts, 1): - assert opt.text == f"Option {i}" - assert opt.get_attribute('value') == str(i) - - assert page.has_no_console_errors() - - def test_displays_options_dict_of_list_of_string_options(self, bokeh_model_page: BokehModelPage) -> None: - select = Select(options=dict(g1=["Option 11"], g2=["Option 21", "Option 22"])) - - page = bokeh_model_page(select) - - el = find_element_for(page.driver, select, "label") - assert el.text == "" - - el = find_element_for(page.driver, select, "select") - grps = el.find_elements(By.TAG_NAME, 'optgroup') - assert len(grps) == 2 - - for i, grp in enumerate(grps, 1): - assert grp.get_attribute('label') == f"g{i}" - opts = grp.find_elements(By.TAG_NAME, 'option') - assert len(opts) == i - for j, opt in enumerate(opts, 1): - assert opt.text == f"Option {i*10 + j}" - assert opt.get_attribute('value') == f"Option {i*10 + j}" - - assert page.has_no_console_errors() - - def test_displays_options_dict_of_list_of_string_options_with_default_value(self, bokeh_model_page: BokehModelPage) -> None: - select = Select(options=dict(g1=["Option 11"], g2=["Option 21", "Option 22"]), value="Option 22") - - page = bokeh_model_page(select) - - label_el = find_element_for(page.driver, select, "label") - assert label_el.text == "" - - select_el = find_element_for(page.driver, select, "select") - assert select_el.get_attribute("value") == "Option 22" - - grps = select_el.find_elements(By.TAG_NAME, 'optgroup') - assert len(grps) == 2 - - for i, grp in enumerate(grps, 1): - assert grp.get_attribute('label') == f"g{i}" - opts = grp.find_elements(By.TAG_NAME, 'option') - assert len(opts) == i - for j, opt in enumerate(opts, 1): - assert opt.text == f"Option {i*10 + j}" - assert opt.get_attribute('value') == f"Option {i*10 + j}" - - assert page.has_no_console_errors() - - def test_displays_dict_of_list_of_tuple_options(self, bokeh_model_page: BokehModelPage) -> None: - select = Select(options=dict(g1=[("11", "Option 11")], g2=[("21", "Option 21"), ("22", "Option 22")])) - - page = bokeh_model_page(select) - - el = find_element_for(page.driver, select, "label") - assert el.text == "" - - el = find_element_for(page.driver, select, "select") - grps = el.find_elements(By.TAG_NAME, 'optgroup') - assert len(grps) == 2 - - for i, grp in enumerate(grps, 1): - assert grp.get_attribute('label') == f"g{i}" - opts = grp.find_elements(By.TAG_NAME, 'option') - assert len(opts) == i - for j, opt in enumerate(opts, 1): - assert opt.text == f"Option {i*10 + j}" - assert opt.get_attribute('value') == f"{i*10 + j}" - - assert page.has_no_console_errors() - - def test_displays_dict_of_list_of_tuple_options_with_default_value(self, bokeh_model_page: BokehModelPage) -> None: - select = Select(options=dict(g1=[("11", "Option 11")], g2=[("21", "Option 21"), ("22", "Option 22")]), value="22") - - page = bokeh_model_page(select) - - label_el = find_element_for(page.driver, select, "label") - assert label_el.text == "" - - select_el = find_element_for(page.driver, select, "select") - assert select_el.get_attribute("value") == "22" - - grps = select_el.find_elements(By.TAG_NAME, 'optgroup') - assert len(grps) == 2 - - for i, grp in enumerate(grps, 1): - assert grp.get_attribute('label') == f"g{i}" - opts = grp.find_elements(By.TAG_NAME, 'option') - assert len(opts) == i - for j, opt in enumerate(opts, 1): - assert opt.text == f"Option {i*10 + j}" - assert opt.get_attribute('value') == f"{i*10 + j}" - - assert page.has_no_console_errors() - - def test_server_on_change_round_trip(self, bokeh_server_page: BokehServerPage) -> None: - select = Select(options=["Option 1", "Option 2", "Option 3"]) - def modify_doc(doc): - source = ColumnDataSource(dict(x=[1, 2], y=[1, 1], val=["a", "b"])) - plot = Plot(height=400, width=400, x_range=Range1d(0, 1), y_range=Range1d(0, 1), min_border=0) - plot.add_glyph(source, Scatter(x='x', y='y', size=20)) - plot.tags.append(CustomJS(name="custom-action", args=dict(s=source), code=RECORD("data", "s.data"))) - def cb(attr, old, new): - source.data['val'] = [old, new] - select.on_change('value', cb) - doc.add_root(column(select, plot)) - - page = bokeh_server_page(modify_doc) - - el = find_element_for(page.driver, select, "select") - el.click() - - el = find_element_for(page.driver, select, 'select option[value="Option 3"]') - el.click() - - page.eval_custom_action() - - results = page.results - assert results['data']['val'] == ["", "Option 3"] - - el = find_element_for(page.driver, select, "select") - el.click() - - el = find_element_for(page.driver, select, 'select option[value="Option 1"]') - el.click() - - page.eval_custom_action() - - results = page.results - assert results['data']['val'] == ["Option 3", "Option 1"] - - assert page.has_no_console_errors() - - def test_js_on_change_executes(self, bokeh_model_page: BokehModelPage) -> None: - select = Select(options=["Option 1", "Option 2", "Option 3"]) - select.js_on_change('value', CustomJS(code=RECORD("value", "cb_obj.value"))) - - page = bokeh_model_page(select) - - el = find_element_for(page.driver, select, "select") - el.click() - - el = find_element_for(page.driver, select, 'select option[value="Option 3"]') - el.click() - - results = page.results - assert results['value'] == 'Option 3' - - assert page.has_no_console_errors() diff --git a/tests/integration/widgets/test_slider.py b/tests/integration/widgets/test_slider.py deleted file mode 100644 index e8eaa070549..00000000000 --- a/tests/integration/widgets/test_slider.py +++ /dev/null @@ -1,267 +0,0 @@ -#----------------------------------------------------------------------------- -# Copyright (c) Anaconda, Inc. All rights reserved. -# -# Powered by the Bokeh Development Team. -# -# The full license is in the file LICENSE.txt, distributed with this software. -#----------------------------------------------------------------------------- - -#----------------------------------------------------------------------------- -# Boilerplate -#----------------------------------------------------------------------------- -from __future__ import annotations # isort:skip - -import pytest ; pytest - -#----------------------------------------------------------------------------- -# Imports -#----------------------------------------------------------------------------- - -# Standard library imports -from time import sleep - -# Bokeh imports -from bokeh.layouts import column -from bokeh.models import ( - ColumnDataSource, - CustomJS, - Plot, - Range1d, - Scatter, - Slider, -) -from tests.support.plugins.project import BokehModelPage, BokehServerPage -from tests.support.util.selenium import ( - RECORD, - Keys, - drag_slider, - find_element_for, - find_elements_for, - get_slider_bar_color, - get_slider_title_text, - get_slider_title_value, - select_element_and_press_key, -) - -#----------------------------------------------------------------------------- -# Tests -#----------------------------------------------------------------------------- - -pytest_plugins = ( - "tests.support.plugins.project", -) - - -@pytest.mark.selenium -class Test_Slider: - def test_display(self, bokeh_model_page: BokehModelPage) -> None: - slider = Slider(start=0, end=10, value=1, width=300) - - page = bokeh_model_page(slider) - - children = find_elements_for(page.driver, slider, "div.bk-input-group > div") - assert len(children) == 2 - - assert page.has_no_console_errors() - - def test_displays_title(self, bokeh_model_page: BokehModelPage) -> None: - slider = Slider(start=0, end=10, value=1, title="bar", width=300) - - page = bokeh_model_page(slider) - - children = find_elements_for(page.driver, slider, "div.bk-input-group > div") - assert len(children) == 2 - - assert get_slider_title_text(page.driver, slider) == "bar: 1" - assert float(get_slider_title_value(page.driver, slider)) == 1 - - assert page.has_no_console_errors() - - def test_title_updates(self, bokeh_model_page: BokehModelPage) -> None: - slider = Slider(start=0, end=10, value=1, title="bar", width=300) - - page = bokeh_model_page(slider) - - assert float(get_slider_title_value(page.driver, slider)) == 1 - - drag_slider(page.driver, slider, 50) - value = get_slider_title_value(page.driver, slider) - assert float(value) > 1 - assert float(value) == int(value) # integral step size - - drag_slider(page.driver, slider, 50) - assert float(get_slider_title_value(page.driver, slider)) > 2 - - drag_slider(page.driver, slider, -135) - assert float(get_slider_title_value(page.driver, slider)) == 0 - - assert page.has_no_console_errors() - - def test_keypress_event(self, bokeh_model_page: BokehModelPage) -> None: - slider = Slider(start=0, end=10, value=1, title="bar", width=300) - page = bokeh_model_page(slider) - - handle = find_element_for(page.driver, slider, ".noUi-handle") - - select_element_and_press_key(page.driver, handle, Keys.ARROW_RIGHT, press_number=1) - assert float(get_slider_title_value(page.driver, slider)) == 2 - select_element_and_press_key(page.driver, handle, Keys.ARROW_LEFT, press_number=3) # hit lower value and continue - assert float(get_slider_title_value(page.driver, slider)) == 0 - select_element_and_press_key(page.driver, handle, Keys.ARROW_RIGHT, press_number=11) # hit higher value and continue - assert float(get_slider_title_value(page.driver, slider)) == 10 - assert page.has_no_console_errors() - - def test_displays_bar_color(self, bokeh_model_page: BokehModelPage) -> None: - slider = Slider(start=0, end=10, value=1, title="bar", width=300, bar_color="red") - page = bokeh_model_page(slider) - - children = find_elements_for(page.driver, slider, "div.bk-input-group > div") - assert len(children) == 2 - - assert get_slider_bar_color(page.driver, slider) == "rgba(255, 0, 0, 1)" - - assert page.has_no_console_errors() - - def test_js_on_change_executes(self, bokeh_model_page: BokehModelPage) -> None: - slider = Slider(start=0, end=10, value=1, title="bar", width=300) - slider.js_on_change('value', CustomJS(code=RECORD("value", "cb_obj.value"))) - - page = bokeh_model_page(slider) - - drag_slider(page.driver, slider, 150) - - results = page.results - assert float(results['value']) > 1 - - assert page.has_no_console_errors() - - def test_server_on_change_round_trip(self, bokeh_server_page: BokehServerPage) -> None: - slider = Slider(start=0, end=10, value=1, title="bar", width=300) - - def modify_doc(doc): - source = ColumnDataSource(dict(x=[1, 2], y=[1, 1], val=["a", "b"])) - plot = Plot(height=400, width=400, x_range=Range1d(0, 1), y_range=Range1d(0, 1), min_border=0) - plot.add_glyph(source, Scatter(x='x', y='y', size=20)) - plot.tags.append(CustomJS(name="custom-action", args=dict(s=source), code=RECORD("data", "s.data"))) - - def cb(attr, old, new): - source.data['val'] = [old, new] - - slider.on_change('value', cb) - doc.add_root(column(slider, plot)) - - page = bokeh_server_page(modify_doc) - - drag_slider(page.driver, slider, 50) - - page.eval_custom_action() - results = page.results - old, new = results['data']['val'] - assert float(old) == 1 - assert float(new) > 1 - - drag_slider(page.driver, slider, 50) - - page.eval_custom_action() - results = page.results - old, new = results['data']['val'] - assert float(new) > 2 - - drag_slider(page.driver, slider, -135) - - page.eval_custom_action() - results = page.results - old, new = results['data']['val'] - assert float(new) == 0 - - # XXX (bev) skip keypress part of test until it can be fixed - # handle = find_element_for(page.driver, slider, ".noUi-handle") - # select_element_and_press_key(page.driver, handle, Keys.ARROW_RIGHT) - - # page.eval_custom_action() - # results = page.results - # old, new = results['data']['val'] - # assert float(new) == 1 - - # XXX (bev) disabled until https://github.com/bokeh/bokeh/issues/7970 is resolved - # assert page.has_no_console_errors() - - def test_server_callback_value_vs_value_throttled(self, bokeh_server_page: BokehServerPage) -> None: - junk = dict(v=0, vt=0) - slider = Slider(start=0, end=10, value=1, title="bar", width=300) - - def modify_doc(doc): - plot = Plot(height=400, width=400, x_range=Range1d(0, 1), y_range=Range1d(0, 1), min_border=0) - - def cbv(attr, old, new): junk['v'] += 1 - def cbvt(attr, old, new): junk['vt'] += 1 - - slider.on_change('value', cbv) - slider.on_change('value_throttled', cbvt) - doc.add_root(column(slider, plot)) - - page = bokeh_server_page(modify_doc) - - drag_slider(page.driver, slider, 30, release=False) - sleep(1) # noUiSlider does a transition that takes some time - - drag_slider(page.driver, slider, 30, release=False) - sleep(1) # noUiSlider does a transition that takes some time - - drag_slider(page.driver, slider, 30, release=False) - sleep(1) # noUiSlider does a transition that takes some time - - drag_slider(page.driver, slider, 30, release=True) - sleep(1) # noUiSlider does a transition that takes some time - - assert junk['v'] == 4 - assert junk['vt'] == 1 - - # XXX (bev) disabled until https://github.com/bokeh/bokeh/issues/7970 is resolved - # assert page.has_no_console_errors() - - def test_server_bar_color_updates(self, bokeh_server_page: BokehServerPage) -> None: - slider = Slider(start=0, end=10, value=1, title="bar", width=300) - - def modify_doc(doc): - plot = Plot(height=400, width=400, x_range=Range1d(0, 1), y_range=Range1d(0, 1), min_border=0) - - def cb(attr, old, new): - slider.bar_color = "rgba(255, 255, 0, 1)" - - slider.on_change('value', cb) - doc.add_root(column(slider, plot)) - - page = bokeh_server_page(modify_doc) - - drag_slider(page.driver, slider, 150) - - sleep(1) # noUiSlider does a transition that takes some time - - assert get_slider_bar_color(page.driver, slider) == "rgba(255, 255, 0, 1)" - - # XXX (bev) disabled until https://github.com/bokeh/bokeh/issues/7970 is resolved - # assert page.has_no_console_errors() - - def test_server_title_updates(self, bokeh_server_page: BokehServerPage) -> None: - slider = Slider(start=0, end=10, value=1, title="bar", width=300) - - def modify_doc(doc): - plot = Plot(height=400, width=400, x_range=Range1d(0, 1), y_range=Range1d(0, 1), min_border=0) - - def cb(attr, old, new): - slider.title = "baz" - - slider.on_change('value', cb) - doc.add_root(column(slider, plot)) - - page = bokeh_server_page(modify_doc) - - drag_slider(page.driver, slider, 150) - - sleep(1) # noUiSlider does a transition that takes some time - - assert get_slider_title_text(page.driver, slider) == "baz: 6" - - # XXX (bev) disabled until https://github.com/bokeh/bokeh/issues/7970 is resolved - # assert page.has_no_console_errors() diff --git a/tests/integration/widgets/test_spinner.py b/tests/integration/widgets/test_spinner.py deleted file mode 100644 index 5e3b6f9ef92..00000000000 --- a/tests/integration/widgets/test_spinner.py +++ /dev/null @@ -1,229 +0,0 @@ -#----------------------------------------------------------------------------- -# Copyright (c) Anaconda, Inc. All rights reserved. -# -# Powered by the Bokeh Development Team. -# -# The full license is in the file LICENSE.txt, distributed with this software. -#----------------------------------------------------------------------------- - -#----------------------------------------------------------------------------- -# Boilerplate -#----------------------------------------------------------------------------- -from __future__ import annotations # isort:skip - -import pytest ; pytest - -#----------------------------------------------------------------------------- -# Imports -#----------------------------------------------------------------------------- - -# Bokeh imports -from bokeh.layouts import column -from bokeh.models import ( - Circle, - ColumnDataSource, - CustomJS, - Plot, - Range1d, - Spinner, -) -from tests.support.plugins.project import BokehModelPage, BokehServerPage -from tests.support.util.selenium import ( - RECORD, - ActionChains, - Keys, - enter_text_in_element, - find_element_for, -) - -#----------------------------------------------------------------------------- -# Tests -#----------------------------------------------------------------------------- - -pytest_plugins = ( - "tests.support.plugins.project", -) - - -def mk_modify_doc(spinner: Spinner): - def modify_doc(doc): - source = ColumnDataSource(dict(x=[1, 2], y=[1, 1], val=["a", "b"])) - plot = Plot(height=400, width=400, x_range=Range1d(0, 1), y_range=Range1d(0, 1), min_border=0) - - plot.add_glyph(source, Circle(x='x', y='y')) - plot.tags.append(CustomJS(name="custom-action", args=dict(s=source), code=RECORD("data", "s.data"))) - - def cb(attr, old, new): - source.data['val'] = [old, new] - - spinner.on_change('value', cb) - doc.add_root(column(spinner, plot)) - return doc - return modify_doc - -@pytest.mark.selenium -class Test_Spinner: - - def test_spinner_display(self, bokeh_model_page: BokehModelPage) -> None: - spinner = Spinner() - - page = bokeh_model_page(spinner) - - input_el = find_element_for(page.driver, spinner, "input") - btn_up_el = find_element_for(page.driver, spinner, ".bk-spin-btn-up") - btn_down_el = find_element_for(page.driver, spinner, ".bk-spin-btn-down") - assert input_el.get_attribute('type') == "text" - assert btn_up_el.tag_name == "button" - assert btn_down_el.tag_name == "button" - - assert page.has_no_console_errors() - - def test_spinner_display_title(self, bokeh_model_page: BokehModelPage) -> None: - spinner = Spinner(title="title") - - page = bokeh_model_page(spinner) - - label_el = find_element_for(page.driver, spinner, "label") - assert label_el.text == "title" - input_el = find_element_for(page.driver, spinner, "input") - assert input_el.get_attribute('type') == "text" - - assert page.has_no_console_errors() - - def test_spinner_value_format(self, bokeh_model_page: BokehModelPage) -> None: - spinner = Spinner(value=1, low=0, high=10, step=1, format="0.00") - - page = bokeh_model_page(spinner) - - input_el = find_element_for(page.driver, spinner, "input") - - assert input_el.get_attribute('value') == '1.00' - - assert page.has_no_console_errors() - - def test_spinner_smallest_step(self, bokeh_model_page: BokehModelPage) -> None: - spinner = Spinner(value=0, low=0, high=1, step=1e-16) - spinner.js_on_change('value', CustomJS(code=RECORD("value", "cb_obj.value"))) - - page = bokeh_model_page(spinner) - - input_el = find_element_for(page.driver, spinner, "input") - - enter_text_in_element(page.driver, input_el, "0.43654644333534") - results = page.results - assert results['value'] == 0.43654644333534 - - enter_text_in_element(page.driver, input_el, "1e-16", click=2) - results = page.results - assert results['value'] == 1e-16 - - assert page.has_no_console_errors() - - def test_spinner_spinning_events(self, bokeh_model_page: BokehModelPage) -> None: - spinner = Spinner(value=0, low=0, high=1, step=0.01) - spinner.js_on_change('value', CustomJS(code=RECORD("value", "cb_obj.value"))) - - page = bokeh_model_page(spinner) - - input_el = find_element_for(page.driver, spinner, "input") - btn_up_el = find_element_for(page.driver, spinner, ".bk-spin-btn-up") - btn_down_el = find_element_for(page.driver, spinner, ".bk-spin-btn-down") - - enter_text_in_element(page.driver, input_el, "0.5") - results = page.results - assert results['value'] == 0.5 - - #click btn up - actions = ActionChains(page.driver) - actions.click(on_element=btn_up_el) - actions.perform() - results = page.results - assert results['value'] == 0.51 - - #dbl click btn down - actions = ActionChains(page.driver) - actions.double_click(on_element=btn_down_el) - actions.perform() - results = page.results - assert results['value'] == 0.49 - - #arrow up - actions = ActionChains(page.driver) - actions.click(on_element=input_el) - actions.send_keys(Keys.ARROW_UP) - actions.perform() - results = page.results - assert results['value'] == 0.50 - - #arrow down - actions = ActionChains(page.driver) - actions.click(on_element=input_el) - actions.key_down(Keys.ARROW_DOWN) - actions.perform() - results = page.results - assert results['value'] == 0.49 - - #page up - actions = ActionChains(page.driver) - actions.click(on_element=input_el) - actions.key_down(Keys.PAGE_UP) - actions.perform() - results = page.results - assert results['value'] == 0.59 - - #page down - actions = ActionChains(page.driver) - actions.click(on_element=input_el) - actions.key_down(Keys.PAGE_DOWN) - actions.perform() - results = page.results - assert results['value'] == 0.49 - - assert page.has_no_console_errors() - - def test_server_on_change_round_trip(self, bokeh_server_page: BokehServerPage) -> None: - spinner = Spinner(low=-1, high=10, step=0.1, value=4, format="0[.]0") - page = bokeh_server_page(mk_modify_doc(spinner)) - - input_el = find_element_for(page.driver, spinner, "input") - - # same value - enter_text_in_element(page.driver, input_el, "4", click=2) - page.eval_custom_action() - results = page.results - assert results['data']['val'] == ["a", "b"] - - # new valid value - enter_text_in_element(page.driver, input_el, "5", click=2) - page.eval_custom_action() - results = page.results - assert results['data']['val'] == [4, 5] - - # new overflow value - enter_text_in_element(page.driver, input_el, "11", click=2) - page.eval_custom_action() - results = page.results - assert results['data']['val'] == [5, 10] - - # new underflow value - enter_text_in_element(page.driver, input_el, "-2", click=2) - page.eval_custom_action() - results = page.results - assert results['data']['val'] == [10, -1] - - # new decimal value - input_el.clear() #negative previous values needs a triple click to be selected - enter_text_in_element(page.driver, input_el, "5.1") - page.eval_custom_action() - results = page.results - assert results['data']['val'] == [None, 5.1] - - # new decimal value test rounding - enter_text_in_element(page.driver, input_el, "5.19", click=2) - page.eval_custom_action() - results = page.results - assert results['data']['val'] == [5.1, 5.19] - assert input_el.get_attribute('value') == '5.2' - - # XXX (bev) disabled until https://github.com/bokeh/bokeh/issues/7970 is resolved - # assert page.has_no_console_errors() diff --git a/tests/integration/widgets/test_text_input.py b/tests/integration/widgets/test_text_input.py deleted file mode 100644 index 70880c9f5a3..00000000000 --- a/tests/integration/widgets/test_text_input.py +++ /dev/null @@ -1,168 +0,0 @@ -#----------------------------------------------------------------------------- -# Copyright (c) Anaconda, Inc. All rights reserved. -# -# Powered by the Bokeh Development Team. -# -# The full license is in the file LICENSE.txt, distributed with this software. -#----------------------------------------------------------------------------- - -#----------------------------------------------------------------------------- -# Boilerplate -#----------------------------------------------------------------------------- -from __future__ import annotations # isort:skip - -import pytest ; pytest - -#----------------------------------------------------------------------------- -# Imports -#----------------------------------------------------------------------------- - -# Bokeh imports -from bokeh.application.handlers.function import ModifyDoc -from bokeh.layouts import column -from bokeh.models import ( - ColumnDataSource, - CustomJS, - Plot, - Range1d, - Scatter, - TextInput, -) -from tests.support.plugins.project import ( - BokehModelPage, - BokehServerPage, - SinglePlotPage, -) -from tests.support.util.selenium import RECORD, enter_text_in_element, find_element_for - -#----------------------------------------------------------------------------- -# Tests -#----------------------------------------------------------------------------- - -pytest_plugins = ( - "tests.support.plugins.project", -) - -def mk_modify_doc(text_input: TextInput) -> tuple[ModifyDoc, Plot]: - plot = Plot(height=400, width=400, x_range=Range1d(0, 1), y_range=Range1d(0, 1), min_border=0) - def modify_doc(doc): - source = ColumnDataSource(dict(x=[1, 2], y=[1, 1], val=["a", "b"])) - plot.add_glyph(source, Scatter(x='x', y='y', size=20)) - plot.tags.append(CustomJS(name="custom-action", args=dict(s=source), code=RECORD("data", "s.data"))) - def cb(attr, old, new): - source.data['val'] = [old, new] - text_input.on_change('value', cb) - doc.add_root(column(text_input, plot)) - return modify_doc, plot - -@pytest.mark.selenium -class Test_TextInput: - def test_displays_text_input(self, bokeh_model_page: BokehModelPage) -> None: - text_input = TextInput() - page = bokeh_model_page(text_input) - - el = find_element_for(page.driver, text_input, "input") - assert el.get_attribute('type') == "text" - - assert page.has_no_console_errors() - - def test_displays_title(self, bokeh_model_page: BokehModelPage) -> None: - text_input = TextInput(title="title") - page = bokeh_model_page(text_input) - - el = find_element_for(page.driver, text_input, "label") - assert el.text == "title" - el = find_element_for(page.driver, text_input, "input") - assert el.get_attribute('placeholder') == "" - assert el.get_attribute('type') == "text" - - assert page.has_no_console_errors() - - def test_displays_placeholder(self, bokeh_model_page: BokehModelPage) -> None: - text_input = TextInput(placeholder="placeholder") - page = bokeh_model_page(text_input) - - el = find_element_for(page.driver, text_input, "label") - assert el.text == "" - el = find_element_for(page.driver, text_input, "input") - assert el.get_attribute('placeholder') == "placeholder" - assert el.get_attribute('type') == "text" - - assert page.has_no_console_errors() - - def test_server_on_change_no_round_trip_without_enter_or_click(self, bokeh_server_page: BokehServerPage) -> None: - text_input = TextInput() - modify_doc, _ = mk_modify_doc(text_input) - page = bokeh_server_page(modify_doc) - - el = find_element_for(page.driver, text_input, "input") - enter_text_in_element(page.driver, el, "pre", enter=False) # not change event if enter is not pressed - - page.eval_custom_action() - - results = page.results - assert results['data']['val'] == ["a", "b"] - - assert page.has_no_console_errors() - - def test_server_on_change_round_trip(self, bokeh_server_page: BokehServerPage) -> None: - text_input = TextInput() - modify_doc, plot = mk_modify_doc(text_input) - page = bokeh_server_page(modify_doc) - - el = find_element_for(page.driver, text_input, "input") - enter_text_in_element(page.driver, el, "val1") - - page.eval_custom_action() - - results = page.results - assert results['data']['val'] == ["", "val1"] - - # double click to highlight and overwrite old text - enter_text_in_element(page.driver, el, "val2", click=2) - - page.eval_custom_action() - - results = page.results - assert results['data']['val'] == ["val1", "val2"] - - # Check clicking outside input also triggers - enter_text_in_element(page.driver, el, "val3", click=2, enter=False) - page.click_canvas_at_position(plot, 10, 10) - - page.eval_custom_action() - - results = page.results - assert results['data']['val'] == ["val2", "val3"] - - assert page.has_no_console_errors() - - def test_js_on_change_executes(self, single_plot_page: SinglePlotPage) -> None: - source = ColumnDataSource(dict(x=[1, 2], y=[1, 1])) - plot = Plot(height=400, width=400, x_range=Range1d(0, 1), y_range=Range1d(0, 1), min_border=0) - plot.add_glyph(source, Scatter(x='x', y='y', size=20)) - text_input = TextInput() - text_input.js_on_change('value', CustomJS(code=RECORD("value", "cb_obj.value"))) - - page = single_plot_page(column(text_input, plot)) - - el = find_element_for(page.driver, text_input, "input") - enter_text_in_element(page.driver, el, "val1") - - results = page.results - assert results['value'] == 'val1' - - # double click to highlight and overwrite old text - enter_text_in_element(page.driver, el, "val2", click=2) - - results = page.results - assert results['value'] == 'val2' - - # Check clicking outside input also triggers - enter_text_in_element(page.driver, el, "val3", click=2, enter=False) - page.click_canvas_at_position(plot, 10, 10) - results = page.results - - assert results['value'] == 'val3' - - assert page.has_no_console_errors() diff --git a/tests/integration/widgets/test_textarea_input.py b/tests/integration/widgets/test_textarea_input.py deleted file mode 100644 index 545f21feba1..00000000000 --- a/tests/integration/widgets/test_textarea_input.py +++ /dev/null @@ -1,85 +0,0 @@ -#----------------------------------------------------------------------------- -# Copyright (c) Anaconda, Inc. All rights reserved. -# -# Powered by the Bokeh Development Team. -# -# The full license is in the file LICENSE.txt, distributed with this software. -#----------------------------------------------------------------------------- - -#----------------------------------------------------------------------------- -# Boilerplate -#----------------------------------------------------------------------------- -from __future__ import annotations # isort:skip - -import pytest ; pytest - -#----------------------------------------------------------------------------- -# Imports -#----------------------------------------------------------------------------- - -# External imports -from selenium.webdriver.common.keys import Keys - -# Bokeh imports -from bokeh.layouts import column -from bokeh.models import ( - ColumnDataSource, - CustomJS, - Plot, - Range1d, - Scatter, - TextAreaInput, -) -from tests.support.plugins.project import BokehModelPage, BokehServerPage -from tests.support.util.selenium import RECORD, enter_text_in_element, find_element_for - -#----------------------------------------------------------------------------- -# Tests -#----------------------------------------------------------------------------- - -pytest_plugins = ( - "tests.support.plugins.project", -) - -foo = [] - -def mk_modify_doc(text_input: TextAreaInput): - def modify_doc(doc): - source = ColumnDataSource(dict(x=[1, 2], y=[1, 1], val=["a", "b"])) - plot = Plot(height=400, width=400, x_range=Range1d(0, 1), y_range=Range1d(0, 1), min_border=0) - plot.add_glyph(source, Scatter(x='x', y='y', size=20)) - code = RECORD("data", "s.data") - plot.tags.append(CustomJS(name="custom-action", args=dict(s=source), code=code)) - def cb(attr, old, new): - foo.append((old, new)) - source.data['val'] = [old, new] - text_input.on_change('value', cb) - doc.add_root(column(text_input, plot)) - return modify_doc - -@pytest.mark.selenium -class Test_TextInput: - def test_displays_text_input(self, bokeh_model_page: BokehModelPage) -> None: - text_input = TextAreaInput() - page = bokeh_model_page(text_input) - el = find_element_for(page.driver, text_input, "textarea") - assert el.tag_name == 'textarea' - assert page.has_no_console_errors() - - def test_displays_placeholder(self, bokeh_model_page: BokehModelPage) -> None: - text_input = TextAreaInput(placeholder="placeholder") - page = bokeh_model_page(text_input) - el = find_element_for(page.driver, text_input, "textarea") - assert el.get_attribute('placeholder') == "placeholder" - assert page.has_no_console_errors() - - def test_server_on_change_round_trip(self, bokeh_server_page: BokehServerPage) -> None: - text_input = TextAreaInput(cols=20) - page = bokeh_server_page(mk_modify_doc(text_input)) - - el = find_element_for(page.driver, text_input, "textarea") - enter_text_in_element(page.driver, el, "val1" + Keys.TAB) - - page.eval_custom_action() - results = page.results - assert results['data']['val'] == ["", "val1"] diff --git a/tests/integration/widgets/test_toggle.py b/tests/integration/widgets/test_toggle.py deleted file mode 100644 index e7f36aba48a..00000000000 --- a/tests/integration/widgets/test_toggle.py +++ /dev/null @@ -1,131 +0,0 @@ -#----------------------------------------------------------------------------- -# Copyright (c) Anaconda, Inc. All rights reserved. -# -# Powered by the Bokeh Development Team. -# -# The full license is in the file LICENSE.txt, distributed with this software. -#----------------------------------------------------------------------------- - -#----------------------------------------------------------------------------- -# Boilerplate -#----------------------------------------------------------------------------- -from __future__ import annotations # isort:skip - -import pytest ; pytest - -#----------------------------------------------------------------------------- -# Imports -#----------------------------------------------------------------------------- - -# Bokeh imports -from bokeh.core.enums import ButtonType -from bokeh.layouts import column -from bokeh.models import ( - ColumnDataSource, - CustomJS, - Plot, - Range1d, - Scatter, - Toggle, -) -from tests.support.plugins.project import BokehModelPage, BokehServerPage -from tests.support.util.selenium import RECORD, find_element_for - -#----------------------------------------------------------------------------- -# Tests -#----------------------------------------------------------------------------- - -pytest_plugins = ( - "tests.support.plugins.project", -) - - -@pytest.mark.selenium -class Test_Toggle: - def test_displays_label(self, bokeh_model_page: BokehModelPage) -> None: - button = Toggle(label="label") - - page = bokeh_model_page(button) - - button = find_element_for(page.driver, button, ".bk-btn") - assert button.text == "label" - - @pytest.mark.parametrize('typ', list(ButtonType)) - def test_displays_button_type(self, typ, bokeh_model_page: BokehModelPage) -> None: - button = Toggle(button_type=typ) - - page = bokeh_model_page(button) - - button = find_element_for(page.driver, button, ".bk-btn") - assert typ in button.get_attribute('class') - - def test_server_on_click_round_trip(self, bokeh_server_page: BokehServerPage) -> None: - button = Toggle() - def modify_doc(doc): - source = ColumnDataSource(dict(x=[1, 2], y=[1, 1])) - plot = Plot(height=400, width=400, x_range=Range1d(0, 1), y_range=Range1d(0, 1), min_border=0) - plot.add_glyph(source, Scatter(x='x', y='y', size=20)) - plot.tags.append(CustomJS(name="custom-action", args=dict(s=source), code=RECORD("data", "s.data"))) - def cb(event): - if button.active: - source.data=dict(x=[10, 20], y=[10, 10]) - else: - source.data=dict(x=[100, 200], y=[100, 100]) - button.on_event('button_click', cb) - doc.add_root(column(button, plot)) - - page = bokeh_server_page(modify_doc) - - button_el = find_element_for(page.driver, button, ".bk-btn") - button_el.click() - - page.eval_custom_action() - - results = page.results - assert results == {'data': {'x': [10, 20], 'y': [10, 10]}} - - button_el = find_element_for(page.driver, button, ".bk-btn") - button_el.click() - - page.eval_custom_action() - - results = page.results - assert results == {'data': {'x': [100, 200], 'y': [100, 100]}} - - button_el = find_element_for(page.driver, button, ".bk-btn") - button_el.click() - - page.eval_custom_action() - - results = page.results - assert results == {'data': {'x': [10, 20], 'y': [10, 10]}} - - assert page.has_no_console_errors() - - # XXX (bev) Toggle does not register to process ButtonClick events - - def test_js_on_click_executes(self, bokeh_model_page: BokehModelPage) -> None: - button = Toggle() - button.js_on_event('button_click', CustomJS(code=RECORD("value", "cb_obj.origin.active"))) - - page = bokeh_model_page(button) - - button_el = find_element_for(page.driver, button, ".bk-btn") - button_el.click() - - results = page.results - assert results == {'value': True} - - button_el = find_element_for(page.driver, button, ".bk-btn") - button_el.click() - - results = page.results - assert results == {'value': False} - - button_el = find_element_for(page.driver, button, ".bk-btn") - button_el.click() - - results = page.results - assert results == {'value': True} - - assert page.has_no_console_errors() diff --git a/tests/support/plugins/project.py b/tests/support/plugins/project.py index 478c713a63e..4762143f347 100644 --- a/tests/support/plugins/project.py +++ b/tests/support/plugins/project.py @@ -21,45 +21,12 @@ #----------------------------------------------------------------------------- # Standard library imports -import socket -import time -from contextlib import closing -from threading import Thread -from typing import ( - TYPE_CHECKING, - Any, - Callable, - Protocol, -) +from typing import TYPE_CHECKING # External imports import pytest -from selenium.webdriver.common.action_chains import ActionChains -from selenium.webdriver.support import expected_conditions as EC -from selenium.webdriver.support.wait import WebDriverWait -from tornado.ioloop import IOLoop -from tornado.web import RequestHandler - -if TYPE_CHECKING: - from selenium.webdriver.common.keys import _KeySeq - from selenium.webdriver.remote.webdriver import WebDriver - from selenium.webdriver.remote.webelement import WebElement - -# Bokeh imports -import bokeh.server.views.ws as ws -from bokeh.application.handlers.function import ModifyDoc -from bokeh.io import save -from bokeh.models import LayoutDOM, Plot -from bokeh.server.server import Server -from tests.support.util.selenium import ( - INIT, - RESULTS, - find_matching_element, - get_events_el, -) if TYPE_CHECKING: - from bokeh.model import Model from tests.support.plugins.file_server import SimpleWebServer #----------------------------------------------------------------------------- @@ -69,16 +36,9 @@ pytest_plugins = ( "tests.support.plugins.project", "tests.support.plugins.file_server", - "tests.support.plugins.selenium", ) __all__ = ( - 'bokeh_app_info', - 'bokeh_model_page', - 'bokeh_server_page', - 'find_free_port', - 'output_file_url', - 'single_plot_page', 'test_file_path_and_url', ) @@ -86,21 +46,6 @@ # General API #----------------------------------------------------------------------------- -@pytest.fixture -def output_file_url(request: pytest.FixtureRequest, file_server: SimpleWebServer) -> str: - from bokeh.io import output_file - file_name = request.function.__name__ + '.html' - file_path = request.node.path.with_name(file_name) - - output_file(file_path, mode='inline') - - def tear_down() -> None: - if file_path.is_file(): - file_path.unlink() - request.addfinalizer(tear_down) - - return file_server.where_is(file_path) - @pytest.fixture def test_file_path_and_url(request: pytest.FixtureRequest, file_server: SimpleWebServer) -> tuple[str, str]: file_name = request.function.__name__ + '.html' @@ -112,254 +57,3 @@ def tear_down() -> None: request.addfinalizer(tear_down) return file_path, file_server.where_is(file_path) - -class _ExitHandler(RequestHandler): - def initialize(self, io_loop: IOLoop) -> None: - self.io_loop = io_loop - async def get(self, *args: Any, **kwargs: Any) -> None: - self.io_loop.stop() - - -def find_free_port() -> int: - with closing(socket.socket(socket.AF_INET, socket.SOCK_STREAM)) as s: - s.bind(('', 0)) - return s.getsockname()[1] - -class BokehAppInfo(Protocol): - def __call__(self, modify_doc: ModifyDoc) -> tuple[str, ws.MessageTestPort]: ... - -class HasNoConsoleErrors(Protocol): - def __call__(self, webdriver: WebDriver) -> bool: ... - -@pytest.fixture -def bokeh_app_info(request: pytest.FixtureRequest, driver: WebDriver) -> BokehAppInfo: - ''' Start a Bokeh server app and return information needed to test it. - - Returns a tuple (url, message_test_port), where the latter is an instance of - ``MessageTestPort`` dataclass, and will contain all messages that the Bokeh - Server sends/receives while running during the test. - - ''' - - def func(modify_doc: ModifyDoc) -> tuple[str, ws.MessageTestPort]: - ws._message_test_port = ws.MessageTestPort(sent=[], received=[]) - port = find_free_port() - def worker() -> None: - io_loop = IOLoop() - server = Server({'/': modify_doc}, - port=port, - io_loop=io_loop, - extra_patterns=[('/exit', _ExitHandler, dict(io_loop=io_loop))]) - server.start() - server.io_loop.start() - - t = Thread(target=worker) - t.start() - - def cleanup() -> None: - driver.get(f"http://localhost:{port}/exit") - - # XXX (bev) this line is a workaround for https://github.com/bokeh/bokeh/issues/7970 - # and should be removed when that issue is resolved - driver.get_log('browser') - - ws._message_test_port = None - t.join() - - request.addfinalizer(cleanup) - - return f"http://localhost:{port}/", ws._message_test_port - - return func - -class _ElementMixin: - _driver: WebDriver - - def click_element_at_position(self, element: WebElement, x: int, y: int) -> None: - actions = ActionChains(self._driver) - actions.move_to_element_with_offset(element, x, y) - actions.click() - actions.perform() - - def double_click_element_at_position(self, element: WebElement, x: int, y: int) -> None: - actions = ActionChains(self._driver) - actions.move_to_element_with_offset(element, x, y) - actions.click() - actions.click() - actions.perform() - - def drag_element_at_position(self, element: WebElement, x: int, y: int, dx: int, dy: int, mod: _KeySeq | None = None) -> None: - actions = ActionChains(self._driver) - if mod: - actions.key_down(mod) - actions.move_to_element_with_offset(element, x, y) - actions.click_and_hold() - actions.move_by_offset(dx, dy) - actions.release() - if mod: - actions.key_up(mod) - actions.perform() - - def send_keys(self, *keys: _KeySeq) -> None: - actions = ActionChains(self._driver) - actions.send_keys(*keys) - actions.perform() - -class _CanvasMixin(_ElementMixin): - canvas: WebElement - - def click_canvas_at_position(self, plot: Plot, x: int, y: int) -> None: - events_el = get_events_el(self._driver, plot) - self.click_element_at_position(events_el, x, y) - - def double_click_canvas_at_position(self, plot: Plot, x: int, y: int) -> None: - events_el = get_events_el(self._driver, plot) - self.double_click_element_at_position(events_el, x, y) - - def drag_canvas_at_position(self, plot: Plot, x: int, y: int, dx: int, dy: int, mod: _KeySeq | None = None) -> None: - events_el = get_events_el(self._driver, plot) - self.drag_element_at_position(events_el, x, y, dx, dy, mod) - - def eval_custom_action(self) -> None: - return self._driver.execute_script('Bokeh.documents[0].get_model_by_name("custom-action").execute()') - - def get_toolbar_buttons(self, plot: Plot) -> list[WebElement]: - script = """ - const toolbar_id = arguments[0] - const toolbar_view = Bokeh.index.get_one_by_id(toolbar_id) - return toolbar_view.model.tools.map((tool) => toolbar_view.owner.query_one((btn) => btn.model.tool == tool).el) - """ - buttons = self._driver.execute_script(script, plot.toolbar.id) - return buttons - -class _BokehPageMixin(_ElementMixin): - - test_div: WebElement - _driver: WebDriver - _has_no_console_errors: HasNoConsoleErrors - - @property - def results(self) -> dict[str, Any]: - WebDriverWait(self._driver, 10).until(EC.staleness_of(self.test_div)) - self.test_div = find_matching_element(self._driver, ".bokeh-test-div") - return self._driver.execute_script(RESULTS) - - @property - def driver(self) -> WebDriver: - return self._driver - - def init_results(self) -> None: - self._driver.execute_script(INIT) - self.test_div = find_matching_element(self._driver, ".bokeh-test-div") - - def has_no_console_errors(self) -> bool: - return self._has_no_console_errors(self._driver) - -class _BokehModelPage(_BokehPageMixin): - - def __init__(self, model: LayoutDOM, driver: WebDriver, output_file_url: str, has_no_console_errors: HasNoConsoleErrors) -> None: - self._driver = driver - self._model = model - self._has_no_console_errors = has_no_console_errors - - save(self._model) - self._driver.get(output_file_url) - self.init_results() - - await_ready(driver, model) - -BokehModelPage = Callable[[LayoutDOM], _BokehModelPage] - -@pytest.fixture() -def bokeh_model_page(driver: WebDriver, output_file_url: str, has_no_console_errors: HasNoConsoleErrors) -> BokehModelPage: - def func(model: LayoutDOM) -> _BokehModelPage: - return _BokehModelPage(model, driver, output_file_url, has_no_console_errors) - return func - -class _SinglePlotPage(_BokehModelPage, _CanvasMixin): - - # model may be a layout, but should only contain a single plot - def __init__(self, model: LayoutDOM, driver: WebDriver, output_file_url: str, has_no_console_errors: HasNoConsoleErrors) -> None: - super().__init__(model, driver, output_file_url, has_no_console_errors) - -SinglePlotPage = Callable[[LayoutDOM], _SinglePlotPage] - -@pytest.fixture() -def single_plot_page(driver: WebDriver, output_file_url: str, - has_no_console_errors: HasNoConsoleErrors) -> SinglePlotPage: - def func(model: LayoutDOM) -> _SinglePlotPage: - return _SinglePlotPage(model, driver, output_file_url, has_no_console_errors) - return func - -class _BokehServerPage(_BokehPageMixin, _CanvasMixin): - - def __init__(self, modify_doc: ModifyDoc, driver: WebDriver, bokeh_app_info: BokehAppInfo, has_no_console_errors: HasNoConsoleErrors) -> None: - self._driver = driver - self._has_no_console_errors = has_no_console_errors - - self._app_url, self.message_test_port = bokeh_app_info(modify_doc) - time.sleep(0.1) - self._driver.get(self._app_url) - - self.init_results() - - def ready(driver: WebDriver) -> bool: - try: - await_all_ready(driver) - return True - except RuntimeError: - return False - WebDriverWait(self._driver, 10).until(ready) - -BokehServerPage = Callable[[ModifyDoc], _BokehServerPage] - -@pytest.fixture() -def bokeh_server_page(driver: WebDriver, bokeh_app_info: BokehAppInfo, - has_no_console_errors: HasNoConsoleErrors) -> BokehServerPage: - def func(modify_doc: ModifyDoc) -> _BokehServerPage: - return _BokehServerPage(modify_doc, driver, bokeh_app_info, has_no_console_errors) - return func - -def await_ready(driver: WebDriver, root: Model) -> None: - script = """ - const [root_id, done] = [...arguments]; - (async function() { - const view = Bokeh.index.get_by_id(root_id) - if (view == null) - done(false) - else { - await view.ready - done(true) - } - })() - """ - if not driver.execute_async_script(script, root.id): - raise RuntimeError(f"could not find a root view for {root}") - -def await_all_ready(driver: WebDriver) -> None: - script = """ - const [done] = [...arguments]; - (async function() { - const views = Bokeh.index.roots - if (views.length == 0) - done(false) - else { - await Promise.all(views.map((view) => view.ready)) - done(true) - } - })() - """ - if not driver.execute_async_script(script): - raise RuntimeError("could not find any root views") - -#----------------------------------------------------------------------------- -# Dev API -#----------------------------------------------------------------------------- - -#----------------------------------------------------------------------------- -# Private API -#----------------------------------------------------------------------------- - -#----------------------------------------------------------------------------- -# Code -#----------------------------------------------------------------------------- diff --git a/tests/support/util/selenium.py b/tests/support/util/selenium.py deleted file mode 100644 index 00b9e2635ca..00000000000 --- a/tests/support/util/selenium.py +++ /dev/null @@ -1,452 +0,0 @@ -#----------------------------------------------------------------------------- -# Copyright (c) Anaconda, Inc., and Bokeh Contributors. -# All rights reserved. -# -# The full license is in the file LICENSE.txt, distributed with this software. -#----------------------------------------------------------------------------- -''' Provide tools for executing Selenium tests. - -''' - -#----------------------------------------------------------------------------- -# Boilerplate -#----------------------------------------------------------------------------- -from __future__ import annotations - -import logging # isort:skip -log = logging.getLogger(__name__) - -#----------------------------------------------------------------------------- -# Imports -#----------------------------------------------------------------------------- - -# Standard library imports -from typing import TYPE_CHECKING, Any, Sequence - -# External imports -from selenium.webdriver.common.action_chains import ActionChains -from selenium.webdriver.common.by import By -from selenium.webdriver.common.keys import Keys - -if TYPE_CHECKING: - from selenium.webdriver.common.keys import _KeySeq - from selenium.webdriver.remote.webdriver import WebDriver - from selenium.webdriver.remote.webelement import WebElement - -# Bokeh imports -from bokeh.models import Button - -if TYPE_CHECKING: - from bokeh.model import Model - from bokeh.models.callbacks import Callback - from bokeh.models.plots import Plot - from bokeh.models.widgets import Slider - from bokeh.models.widgets.tables import DataTable - -#----------------------------------------------------------------------------- -# Globals and constants -#----------------------------------------------------------------------------- - -__all__ = ( - 'alt_click', - 'ButtonWrapper', - 'copy_table_rows', - 'COUNT', - 'drag_range_slider', - 'drag_slider', - 'element_to_finish_resizing', - 'element_to_start_resizing', - 'enter_text_in_cell', - 'enter_text_in_cell_with_click_enter', - 'enter_text_in_element', - 'get_slider_bar_color', - 'get_slider_title_text', - 'get_slider_title_value', - 'get_table_cell', - 'get_table_column_cells', - 'get_table_header', - 'get_table_row', - 'get_table_selected_rows', - 'hover_element', - 'INIT', - 'paste_values', - 'RECORD', - 'RESULTS', - 'SCROLL', - 'select_element_and_press_key', - 'shift_click', - 'sort_table_column', -) - -#----------------------------------------------------------------------------- -# General API -#----------------------------------------------------------------------------- - -MATCHES_SCRIPT = """ - function* descend(el, sel, parent) { - if (el.matches(sel)) { - yield parent ? el.parentElement : el - } - if (el.shadowRoot) { - for (const child of el.shadowRoot.children) { - yield* descend(child, sel, parent) - } - } - for (const child of el.children) { - yield* descend(child, sel, parent) - } - } - - const selector = arguments[0] - const root = arguments[1] ?? document.documentElement - const parent = arguments[2] ?? false - - return [...descend(root, selector, parent)] -""" - -def find_matching_elements(driver: WebDriver, selector: str, *, root: WebElement | None = None, parent: bool = False) -> list[WebElement]: - return driver.execute_script(MATCHES_SCRIPT, selector, root, parent) - -def find_matching_element(driver: WebDriver, selector: str, *, root: WebElement | None = None, parent: bool = False) -> WebElement: - elements = find_matching_elements(driver, selector, root=root, parent=parent) - n = len(elements) - if n == 0: - raise ValueError("not found") - else: - return elements[0] - #elif n == 1: - # return elements[0] - #else: - # raise ValueError("multiple elements found") - -FIND_VIEW_SCRIPT = """ - function* find(views, id, fn) { - for (const view of views) { - if (view.model.id == id) { - yield* fn(view) - } else if ("child_views" in view) { - yield* find(view.child_views, id, fn) - } else if ("tool_views" in view) { - yield* find(view.tool_views.values(), id, fn) - } else if ("renderer_views" in view) { - yield* find(view.renderer_views.values(), id, fn) - } - } - } - - function head(iter) { - for (const item of iter) { - return item - } - return undefined - } -""" - -def get_events_el(driver: WebDriver, model: Plot) -> WebElement: - script = FIND_VIEW_SCRIPT + """ - const id = arguments[0] - function* fn(view) { - yield view.canvas_view.events_el - } - return head(find(Bokeh.index, id, fn)) ?? null - """ - el = driver.execute_script(script, model.id) - if el is not None: - return el - else: - raise RuntimeError(f"can't resolve a view for {model}") - -FIND_SCRIPT = """ - const id = arguments[0] - const selector = arguments[1] - - function* find(views) { - for (const view of views) { - if (view.model.id == id) { - if (selector != null) { - const el = view.shadow_el ?? view.el - yield [...el.querySelectorAll(selector)] - } else - yield [view.el] - } else if ("child_views" in view) { - yield* find(view.child_views) - } else if ("tool_views" in view) { - yield* find(view.tool_views.values()) - } else if ("renderer_views" in view) { - yield* find(view.renderer_views.values()) - } - } - } -""" - -def find_elements_for(driver: WebDriver, model: Model, selector: str | None = None) -> list[WebElement]: - script = FIND_SCRIPT + """ - for (const els of find(Bokeh.index)) { - return els - } - return null - """ - return driver.execute_script(script, model.id, selector) - -def find_element_for(driver: WebDriver, model: Model, selector: str | None = None) -> WebElement: - script = FIND_SCRIPT + """ - for (const els of find(Bokeh.index)) { - return els[0] ?? null - } - return null - """ - el = driver.execute_script(script, model.id, selector) - if el is not None: - return el - else: - raise ValueError("not found") - -def COUNT(key: str) -> str: - return f'Bokeh._testing.count({key!r});' - -INIT = 'Bokeh._testing.init();' - -def RECORD(key: str, value: Any, *, final: bool = True) -> str: - if final: - return f"Bokeh._testing.record({key!r}, {value});" - else: - return f"Bokeh._testing.record0({key!r}, {value});" - -RESULTS = 'return Bokeh._testing.results' - -def SCROLL(amt: float) -> str: - return f""" - const elt = Bokeh.index.roots[0].canvas_view.events_el; - const event = new WheelEvent('wheel', {{ deltaY: {amt:f}, clientX: 100, clientY: 100}} ); - elt.dispatchEvent(event); - """ - -def alt_click(driver: WebDriver, element: WebElement) -> None: - actions = ActionChains(driver) - actions.key_down(Keys.META) - actions.click(element) - actions.key_up(Keys.META) - actions.perform() - - -class ButtonWrapper: - def __init__(self, label: str, callback: Callback) -> None: - self.obj = Button(label=label) - self.obj.js_on_event('button_click', callback) - - def click(self, driver: WebDriver) -> None: - button = find_element_for(driver, self.obj, ".bk-btn") - button.click() - -class element_to_start_resizing: - ''' An expectation for checking if an element has started resizing - ''' - - def __init__(self, element: WebElement) -> None: - self.element = element - self.previous_width = self.element.size['width'] - - def __call__(self, driver: WebDriver) -> bool: - current_width = self.element.size['width'] - if self.previous_width != current_width: - return True - else: - self.previous_width = current_width - return False - -class element_to_finish_resizing: - ''' An expectation for checking if an element has finished resizing - - ''' - - def __init__(self, element: WebElement) -> None: - self.element = element - self.previous_width = self.element.size['width'] - - def __call__(self, driver: WebDriver) -> bool: - current_width = self.element.size['width'] - if self.previous_width == current_width: - return True - else: - self.previous_width = current_width - return False - -def select_element_and_press_key(driver: WebDriver, element: WebElement, key: _KeySeq, press_number: int = 1) -> None: - actions = ActionChains(driver) - actions.move_to_element(element) - actions.click() - for _ in range(press_number): - actions = ActionChains(driver) - actions.send_keys_to_element(element, key) - actions.perform() - -def hover_element(driver: WebDriver, element: WebElement) -> None: - hover = ActionChains(driver).move_to_element(element) - hover.perform() - -def enter_text_in_element(driver: WebDriver, element: WebElement, text: str, - click: int = 1, enter: bool = True, mod: _KeySeq | None = None) -> None: - actions = ActionChains(driver) - actions.move_to_element(element) - if click == 1: actions.click() - elif click == 2: actions.double_click() - if enter: - text += Keys.ENTER - if mod: - actions.key_down(mod) - actions.send_keys(text) - if mod: - actions.key_up(mod) - actions.perform() - -def enter_text_in_cell(driver: WebDriver, table: DataTable, row: int, col: int, text: str) -> None: - actions = ActionChains(driver) - cell = get_table_cell(driver, table, row, col) - actions.move_to_element(cell) - actions.double_click() # start editing a cell - actions.perform() - - actions = ActionChains(driver) - cell = get_table_cell(driver, table, row, col) - try: - input = find_matching_element(driver, "input", root=cell) - except ValueError: - return # table.editable == False - actions.move_to_element(input) - actions.click() # XXX: perhaps sleep() would also work; not required when interacting manually - actions.double_click() # select all text and overwrite it in the next step - actions.send_keys(text + Keys.ENTER) - actions.perform() - -def escape_cell(driver: WebDriver, table: DataTable, row: int, col: int) -> None: - cell = get_table_cell(driver, table, row, col) - try: - input = find_matching_element(driver, "input", root=cell) - except ValueError: - return - - actions = ActionChains(driver) - actions.move_to_element(input) - actions.send_keys(Keys.ESCAPE) - actions.perform() - -def enter_text_in_cell_with_click_enter(driver: WebDriver, table: DataTable, row: int, col: int, text: str) -> None: - actions = ActionChains(driver) - cell = get_table_cell(driver, table, row, col) - actions.move_to_element(cell) - actions.click() - actions.send_keys(Keys.ENTER + text + Keys.ENTER) - actions.perform() - -def enter_text_with_click_enter(driver: WebDriver, cell: WebElement, text: str) -> None: - actions = ActionChains(driver) - actions.move_to_element(cell) - actions.click() - actions.send_keys(Keys.ENTER + text + Keys.ENTER) - actions.perform() - -def copy_table_rows(driver: WebDriver, table: DataTable, rows: Sequence[int]) -> None: - actions = ActionChains(driver) - row = get_table_row(driver, table, rows[0]) - actions.move_to_element(row) - actions.click() - actions.key_down(Keys.SHIFT) - for r in rows[1:]: - row = get_table_row(driver, table, r) - actions.move_to_element(row) - actions.click() - actions.key_up(Keys.SHIFT) - actions.key_down(Keys.CONTROL) - actions.send_keys(Keys.INSERT) - actions.key_up(Keys.CONTROL) - # actions.send_keys(Keys.CONTROL, 'c') - actions.perform() - -def paste_values(driver: WebDriver, el: WebElement | None = None) -> None: - actions = ActionChains(driver) - if el: - actions.move_to_element(el) - actions.key_down(Keys.SHIFT) - actions.send_keys(Keys.INSERT) - actions.key_up(Keys.SHIFT) - # actions.send_keys(Keys.CONTROL, 'v') - actions.perform() - -def get_table_column_cells(driver: WebDriver, table: DataTable, col: int) -> list[str]: - result = [] - rows = find_elements_for(driver, table, ".slick-row") - for row in rows: - elt = row.find_element(By.CSS_SELECTOR, '.slick-cell.l%d.r%d' % (col, col)) - result.append(elt.text) - return result - -def get_table_row(driver: WebDriver, table: DataTable, row: int) -> WebElement: - return find_element_for(driver, table, f".slick-row:nth-child({row})") - -def get_table_selected_rows(driver: WebDriver, table: DataTable) -> set[int]: - result = set() - rows = find_elements_for(driver, table, ".slick-row") - for i, row in enumerate(rows): - elt = row.find_element(By.CSS_SELECTOR, '.slick-cell.l1.r1') - if 'selected' in elt.get_attribute('class'): - result.add(i) - return result - -def get_table_cell(driver: WebDriver, table: DataTable, row: int, col: int) -> WebElement: - return find_element_for(driver, table, f".slick-row:nth-child({row}) .r{col}") - -def get_table_header(driver: WebDriver, table: DataTable, col: int) -> WebElement: - return find_element_for(driver, table, f".slick-header-columns .slick-header-column:nth-child({col})") - -def sort_table_column(driver: WebDriver, table: DataTable, col: int, double: bool = False) -> None: - elt = find_element_for(driver, table, f".slick-header-columns .slick-header-column:nth-child({col})") - elt.click() - if double: elt.click() - -def shift_click(driver: WebDriver, element: WebElement) -> None: - actions = ActionChains(driver) - actions.key_down(Keys.SHIFT) - actions.click(element) - actions.key_up(Keys.SHIFT) - actions.perform() - -def drag_slider(driver: WebDriver, slider: Slider, distance: float, release: bool = True) -> None: - handle = find_element_for(driver, slider, ".noUi-handle") - actions = ActionChains(driver) - actions.move_to_element(handle) - actions.click_and_hold() - actions.move_by_offset(distance, 0) - if release: - actions.release() - actions.perform() - -def drag_range_slider(driver: WebDriver, slider: Slider, location: str, distance: float) -> None: - handle = find_element_for(driver, slider, f".noUi-handle-{location}") - actions = ActionChains(driver) - actions.move_to_element(handle) - actions.click_and_hold() - actions.move_by_offset(distance, 0) - actions.release() - actions.perform() - -def get_slider_title_text(driver: WebDriver, slider: Slider) -> str: - return find_element_for(driver, slider, "div.bk-input-group > div.bk-slider-title").text - -def get_slider_title_value(driver: WebDriver, slider: Slider) -> str: - return find_element_for(driver, slider, "div.bk-input-group > div > span.bk-slider-value").text - -def get_slider_bar_color(driver: WebDriver, slider: Slider) -> str: - bar_el = find_element_for(driver, slider, ".noUi-connect") - return bar_el.value_of_css_property("background-color") - -#----------------------------------------------------------------------------- -# Dev API -#----------------------------------------------------------------------------- - -#----------------------------------------------------------------------------- -# Private API -#----------------------------------------------------------------------------- - -#----------------------------------------------------------------------------- -# Code -#-----------------------------------------------------------------------------