diff --git a/.github/workflows/test-python.yml b/.github/workflows/check.yml similarity index 70% rename from .github/workflows/test-python.yml rename to .github/workflows/check.yml index bbac572..8dd2569 100644 --- a/.github/workflows/test-python.yml +++ b/.github/workflows/check.yml @@ -1,4 +1,4 @@ -name: Test +name: Check on: push: @@ -6,16 +6,16 @@ on: - main pull_request: branches: - - main + - "*" schedule: - - cron: "0 0 * * *" + - cron: "0 0 * * 0" jobs: python-source: runs-on: ubuntu-latest strategy: matrix: - python-version: ["3.9", "3.10", "3.11", "3.12"] + python-version: ["3.11", "3.12", "3.13", "3.14"] steps: - uses: actions/checkout@v4 - uses: oven-sh/setup-bun@v2 @@ -95,3 +95,38 @@ jobs: run: pip install --upgrade pip hatch uv - name: Run Python type checker run: hatch run python:type_check + + javascript: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: oven-sh/setup-bun@v2 + with: + bun-version: latest + - uses: actions/setup-python@v5 + with: + python-version: 3.x + - name: Install Python Dependencies + run: pip install --upgrade pip hatch uv + - name: Run Tests + run: hatch run javascript:check + docs: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + - uses: oven-sh/setup-bun@v2 + with: + bun-version: latest + - uses: actions/setup-python@v5 + with: + python-version: 3.x + - name: Install Python Dependencies + run: pip install --upgrade pip hatch uv + - name: Check documentation links + run: hatch run docs:linkcheck + - name: Check docs build + run: hatch run docs:build + - name: Check docs examples + run: hatch fmt docs --check diff --git a/.github/workflows/publish-develop-docs.yml b/.github/workflows/publish-develop.yml similarity index 92% rename from .github/workflows/publish-develop-docs.yml rename to .github/workflows/publish-develop.yml index a1434ba..770c0b1 100644 --- a/.github/workflows/publish-develop-docs.yml +++ b/.github/workflows/publish-develop.yml @@ -1,10 +1,10 @@ -name: Publish Develop Docs +name: Publish Develop on: push: branches: - main jobs: - publish-develop-docs: + docs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 diff --git a/.github/workflows/publish-latest-docs.yml b/.github/workflows/publish-latest-docs.yml deleted file mode 100644 index 0a1e996..0000000 --- a/.github/workflows/publish-latest-docs.yml +++ /dev/null @@ -1,29 +0,0 @@ -name: Publish Latest Docs -on: - release: - types: [published] - -jobs: - deploy: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - with: - fetch-depth: 0 - - uses: oven-sh/setup-bun@v2 - with: - bun-version: latest - - uses: actions/setup-python@v5 - with: - python-version: 3.x - - name: Install dependencies - run: | - pip install --upgrade pip hatch uv - - name: Configure Git - run: | - git config user.name github-actions - git config user.email github-actions@github.com - - name: Publish ${{ github.event.release.name }} Docs - run: hatch run docs:deploy_latest ${{ github.ref_name }} - concurrency: - group: publish-docs diff --git a/.github/workflows/publish-python.yaml b/.github/workflows/publish-python.yaml deleted file mode 100644 index a2228a7..0000000 --- a/.github/workflows/publish-python.yaml +++ /dev/null @@ -1,27 +0,0 @@ -name: Publish Python - -on: - release: - types: [published] - -jobs: - publish-python: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - uses: oven-sh/setup-bun@v2 - with: - bun-version: latest - - name: Set up Python - uses: actions/setup-python@v5 - with: - python-version: "3.x" - - name: Install dependencies - run: pip install --upgrade pip hatch uv - - name: Build Package - run: hatch build --clean - - name: Publish to PyPI - env: - HATCH_INDEX_USER: ${{ secrets.PYPI_USERNAME }} - HATCH_INDEX_AUTH: ${{ secrets.PYPI_PASSWORD }} - run: hatch publish --yes diff --git a/.github/workflows/publish-release.yml b/.github/workflows/publish-release.yml new file mode 100644 index 0000000..5f43163 --- /dev/null +++ b/.github/workflows/publish-release.yml @@ -0,0 +1,49 @@ +name: Publish Release +on: + release: + types: [published] + +jobs: + docs: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + - uses: oven-sh/setup-bun@v2 + with: + bun-version: latest + - uses: actions/setup-python@v5 + with: + python-version: 3.x + - name: Install dependencies + run: pip install --upgrade pip hatch uv + - name: Configure Git + run: | + git config user.name github-actions + git config user.email github-actions@github.com + - name: Publish ${{ github.event.release.name }} Docs + run: hatch run docs:deploy_latest ${{ github.ref_name }} + concurrency: + group: publish-docs + + python: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: oven-sh/setup-bun@v2 + with: + bun-version: latest + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: "3.x" + - name: Install dependencies + run: pip install --upgrade pip hatch uv + - name: Build Package + run: hatch build --clean + - name: Publish to PyPI + env: + HATCH_INDEX_USER: ${{ secrets.PYPI_USERNAME }} + HATCH_INDEX_AUTH: ${{ secrets.PYPI_PASSWORD }} + run: hatch publish --yes diff --git a/.github/workflows/test-docs.yml b/.github/workflows/test-docs.yml deleted file mode 100644 index 0a81d18..0000000 --- a/.github/workflows/test-docs.yml +++ /dev/null @@ -1,33 +0,0 @@ -name: Test - -on: - push: - branches: - - main - pull_request: - branches: - - main - schedule: - - cron: "0 0 * * *" - -jobs: - docs: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - with: - fetch-depth: 0 - - uses: oven-sh/setup-bun@v2 - with: - bun-version: latest - - uses: actions/setup-python@v5 - with: - python-version: 3.x - - name: Install Python Dependencies - run: pip install --upgrade pip hatch uv - - name: Check documentation links - run: hatch run docs:linkcheck - - name: Check docs build - run: hatch run docs:build - - name: Check docs examples - run: hatch fmt docs --check diff --git a/.github/workflows/test-javascript.yml b/.github/workflows/test-javascript.yml deleted file mode 100644 index 5f62c0e..0000000 --- a/.github/workflows/test-javascript.yml +++ /dev/null @@ -1,25 +0,0 @@ -name: Test - -on: - push: - branches: - - main - pull_request: - branches: - - main - -jobs: - javascript: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - uses: oven-sh/setup-bun@v2 - with: - bun-version: latest - - uses: actions/setup-python@v5 - with: - python-version: 3.x - - name: Install Python Dependencies - run: pip install --upgrade pip hatch uv - - name: Run Tests - run: hatch run javascript:check diff --git a/CHANGELOG.md b/CHANGELOG.md index 3bc200b..439508e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,7 +19,9 @@ Don't forget to remove deprecated code on each major release! ## [Unreleased] -- Nothing (yet)! +### Changed + +- Bump required ReactPy version to `2.x` ## [2.0.0] - 2025-06-14 diff --git a/docs/examples/python/nested_routes.py b/docs/examples/python/nested_routes.py index e146d90..bc27274 100644 --- a/docs/examples/python/nested_routes.py +++ b/docs/examples/python/nested_routes.py @@ -46,12 +46,12 @@ def all_messages(): messages = [] for msg in last_messages.values(): - _link = link( + msg_link = link( {"to": f"/messages/with/{'-'.join(msg['with'])}"}, f"Conversation with: {', '.join(msg['with'])}", ) msg_from = f"{'' if msg['from'] is None else '🔴'} {msg['message']}" - messages.append(html.li({"key": msg["id"]}, html.p(_link), msg_from)) + messages.append(html.li({"key": msg["id"]}, html.p(msg_link), msg_from)) return html.div( html.h1("All Messages 💬"), diff --git a/docs/examples/python/use_params.py b/docs/examples/python/use_params.py index 93a4f07..90aab5d 100644 --- a/docs/examples/python/use_params.py +++ b/docs/examples/python/use_params.py @@ -6,7 +6,7 @@ @component def user(): params = use_params() - return html._(html.h1(f"User {params['id']} 👤"), html.p("Nothing (yet).")) + return html(html.h1(f"User {params['id']} 👤"), html.p("Nothing (yet).")) @component diff --git a/docs/examples/python/use_search_params.py b/docs/examples/python/use_search_params.py index faeba5e..8420d8c 100644 --- a/docs/examples/python/use_search_params.py +++ b/docs/examples/python/use_search_params.py @@ -6,7 +6,7 @@ @component def search(): search_params = use_search_params() - return html._(html.h1(f"Search Results for {search_params['query'][0]} 🔍"), html.p("Nothing (yet).")) + return html(html.h1(f"Search Results for {search_params['query'][0]} 🔍"), html.p("Nothing (yet).")) @component diff --git a/docs/mkdocs.yml b/docs/mkdocs.yml index ad4ab0f..a15d438 100644 --- a/docs/mkdocs.yml +++ b/docs/mkdocs.yml @@ -90,7 +90,7 @@ plugins: handlers: python: paths: ["../"] - import: + inventories: - https://reactpy.dev/docs/objects.inv - https://installer.readthedocs.io/en/stable/objects.inv options: diff --git a/docs/src/learn/routers-routes-and-links.md b/docs/src/learn/routers-routes-and-links.md index f185514..a6233ce 100644 --- a/docs/src/learn/routers-routes-and-links.md +++ b/docs/src/learn/routers-routes-and-links.md @@ -9,7 +9,7 @@ The [`browser_router`][reactpy_router.browser_router] component is one possible !!! abstract "Note" The current location is determined based on the browser's current URL and can be found - by checking the [`use_location`][reactpy.backend.hooks.use_location] hook. + by checking the `reactpy.use_location` hook. Here's a basic example showing how to use `#!python browser_router` with two routes. diff --git a/pyproject.toml b/pyproject.toml index f346125..c476f8e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -13,12 +13,12 @@ readme = "README.md" keywords = ["React", "ReactJS", "ReactPy", "components"] license = "MIT" authors = [{ name = "Mark Bakhit", email = "archiethemonger@gmail.com" }] -requires-python = ">=3.9" +requires-python = ">=3.11" classifiers = [ - "Programming Language :: Python :: 3.9", - "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", + "Programming Language :: Python :: 3.13", + "Programming Language :: Python :: 3.14", "Operating System :: OS Independent", "Intended Audience :: Developers", "Intended Audience :: Science/Research", @@ -28,7 +28,11 @@ classifiers = [ "Environment :: Web Environment", "Typing :: Typed", ] -dependencies = ["reactpy>=1.1.0, <2.0.0", "typing_extensions"] +dependencies = [ + "reactpy[asgi]>=2.0.0b10, <3.0.0", + "typing_extensions", + "jsonpointer==3.*", +] dynamic = ["version"] urls.Changelog = "https://reactive-python.github.io/reactpy-router/latest/about/changelog/" urls.Documentation = "https://reactive-python.github.io/reactpy-router/latest/" @@ -46,6 +50,7 @@ artifacts = ["/src/reactpy_router/static/"] [tool.hatch.metadata] license-files = { paths = ["LICENSE.md"] } +allow-direct-references = true [tool.hatch.envs.default] installer = "uv" @@ -62,18 +67,27 @@ artifacts = [] ############################# [tool.hatch.envs.hatch-test] -extra-dependencies = ["pytest-sugar", "anyio", "reactpy[testing,starlette]"] +extra-dependencies = [ + "pytest-sugar", + "anyio", + "playwright", + "uvicorn[standard]", + "asgiref", + "asgi-tools", + "servestatic", + "orjson", +] randomize = true -matrix-name-format = "{variable}-{value}" [[tool.hatch.envs.hatch-test.matrix]] -python = ["3.9", "3.10", "3.11", "3.12"] +python = ["3.11", "3.12", "3.13", "3.14"] [tool.pytest.ini_options] -addopts = """\ - --strict-config - --strict-markers - """ +addopts = ["--strict-config", "--strict-markers"] +filterwarnings = """ + ignore::DeprecationWarning:uvicorn.* + ignore::DeprecationWarning:websockets.* +""" ####################################### # >>> Hatch Documentation Scripts <<< # @@ -84,7 +98,7 @@ template = "docs" dependencies = [ "mkdocs", "mkdocs-git-revision-date-localized-plugin", - "mkdocs-material==9.4.0", + "mkdocs-material", "mkdocs-include-markdown-plugin", "mkdocs-spellcheck[all]", "mkdocs-git-authors-plugin", @@ -124,6 +138,10 @@ type_check = ["pyright src"] detached = true [tool.hatch.envs.javascript.scripts] +build = [ + 'bun install --cwd "src/js"', + 'bun build "src/js/src/index.ts" --outfile "src/reactpy_router/static/bundle.js" --minify', +] check = ['bun install --cwd "src/js"', 'bun run --cwd "src/js" check'] fix = ['bun install --cwd "src/js"', ' bun run --cwd "src/js" format'] @@ -144,6 +162,7 @@ lint.extend-ignore = [ "PLR2004", # Magic value used in comparison "SIM115", # Use context handler for opening files "SLF001", # Private member accessed + "DOC201", # 'Returns:' section in docstring is missing ] lint.preview = true diff --git a/src/js/bun.lockb b/src/js/bun.lockb index ca1a00b..a59a97a 100644 Binary files a/src/js/bun.lockb and b/src/js/bun.lockb differ diff --git a/src/js/package.json b/src/js/package.json index 313fab9..53f9f21 100644 --- a/src/js/package.json +++ b/src/js/package.json @@ -4,13 +4,11 @@ "check": "prettier --check . && eslint" }, "devDependencies": { - "@types/react": "^18.3.11", - "@types/react-dom": "^18.3.1", "eslint": "^9.13.0", "eslint-plugin-react": "^7.37.1", "prettier": "^3.3.3" }, "dependencies": { - "preact": "^10.24.3" + "@reactpy/client": "^1.0.3" } } diff --git a/src/js/src/components.ts b/src/js/src/components.ts index 4712637..1aaff62 100644 --- a/src/js/src/components.ts +++ b/src/js/src/components.ts @@ -1,19 +1,21 @@ -import React from "preact/compat"; -import ReactDOM from "preact/compat"; +import { React } from "@reactpy/client"; import { createLocationObject, pushState, replaceState } from "./utils"; import { HistoryProps, LinkProps, NavigateProps } from "./types"; /** * Interface used to bind a ReactPy node to React. */ -export function bind(node) { +export function bind(node: HTMLElement | Element | Node) { return { - create: (type, props, children) => - React.createElement(type, props, ...children), - render: (element) => { - ReactDOM.render(element, node); + create: ( + type: string, + props: Record, + children: React.ReactNode[], + ) => React.createElement(type, props, ...children), + render: (element: HTMLElement | Element | Node) => { + React.render(element, node); }, - unmount: () => ReactDOM.unmountComponentAtNode(node), + unmount: () => React.render(null, node), }; } diff --git a/src/js/src/types.ts b/src/js/src/types.ts index 7144668..8b05409 100644 --- a/src/js/src/types.ts +++ b/src/js/src/types.ts @@ -1,6 +1,6 @@ export interface ReactPyLocation { - pathname: string; - search: string; + path: string; + query_string: string; } export interface HistoryProps { diff --git a/src/js/src/utils.ts b/src/js/src/utils.ts index e3f1dd5..797c0b5 100644 --- a/src/js/src/utils.ts +++ b/src/js/src/utils.ts @@ -2,8 +2,8 @@ import { ReactPyLocation } from "./types"; export function createLocationObject(): ReactPyLocation { return { - pathname: window.location.pathname, - search: window.location.search, + path: window.location.pathname, + query_string: window.location.search, }; } diff --git a/src/reactpy_router/components.py b/src/reactpy_router/components.py index 6a751e7..d16af57 100644 --- a/src/reactpy_router/components.py +++ b/src/reactpy_router/components.py @@ -5,31 +5,25 @@ from uuid import uuid4 from reactpy import component, html, use_connection, use_ref -from reactpy.backend.types import Location -from reactpy.web.module import export, module_from_file +from reactpy.reactjs import component_from_file +from reactpy.types import Location from reactpy_router.hooks import _use_route_state from reactpy_router.types import Route if TYPE_CHECKING: - from reactpy.core.component import Component - from reactpy.core.types import Key, VdomDict + from reactpy.types import Component, Key, VdomDict -History = export( - module_from_file("reactpy-router", file=Path(__file__).parent / "static" / "bundle.js"), - ("History"), +History = component_from_file( + Path(__file__).parent / "static" / "bundle.js", import_names="History", name="reactpy-router" ) """Client-side portion of history handling""" -Link = export( - module_from_file("reactpy-router", file=Path(__file__).parent / "static" / "bundle.js"), - ("Link"), -) +Link = component_from_file(Path(__file__).parent / "static" / "bundle.js", import_names="Link", name="reactpy-router") """Client-side portion of link handling""" -Navigate = export( - module_from_file("reactpy-router", file=Path(__file__).parent / "static" / "bundle.js"), - ("Navigate"), +Navigate = component_from_file( + Path(__file__).parent / "static" / "bundle.js", import_names="Navigate", name="reactpy-router" ) """Client-side portion of the navigate component""" @@ -74,7 +68,7 @@ def _link(attributes: dict[str, Any], *children: Any) -> VdomDict: def on_click_callback(_event: dict[str, Any]) -> None: set_location(Location(**_event)) - return html._(Link({"onClickCallback": on_click_callback, "linkClass": class_name}), html.a(attrs, *children)) + return html(Link({"onClickCallback": on_click_callback, "linkClass": class_name}), html.a(attrs, *children)) def route(path: str, element: Any | None, *routes: Route) -> Route: @@ -113,12 +107,12 @@ def navigate(to: str, replace: bool = False, key: Key | None = None) -> Componen def _navigate(to: str, replace: bool = False) -> VdomDict | None: location = use_connection().location set_location = _use_route_state().set_location - pathname = to.split("?", 1)[0] + new_path = to.split("?", 1)[0] def on_navigate_callback(_event: dict[str, Any]) -> None: set_location(Location(**_event)) - if location.pathname != pathname: + if location.path != new_path: return Navigate({"onNavigateCallback": on_navigate_callback, "to": to, "replace": replace}) return None diff --git a/src/reactpy_router/hooks.py b/src/reactpy_router/hooks.py index 2480440..0431d11 100644 --- a/src/reactpy_router/hooks.py +++ b/src/reactpy_router/hooks.py @@ -5,7 +5,7 @@ from reactpy import create_context, use_context, use_location -from reactpy_router.types import RouteState # noqa: TCH001 +from reactpy_router.types import RouteState # noqa: TC001 if TYPE_CHECKING: from reactpy.types import Context @@ -59,7 +59,7 @@ def use_search_params( A dictionary of the current URL's query string parameters. """ location = use_location() - query_string = location.search[1:] if len(location.search) > 1 else "" + query_string = location.query_string[1:] if len(location.query_string) > 1 else "" # TODO: In order to match `react-router`, this will need to return a tuple of the search params \ # and a function to update them. This is currently not possible without reactpy core having a \ diff --git a/src/reactpy_router/resolvers.py b/src/reactpy_router/resolvers.py index 58e7b7f..3ab6261 100644 --- a/src/reactpy_router/resolvers.py +++ b/src/reactpy_router/resolvers.py @@ -73,9 +73,7 @@ def resolve(self, path: str) -> MatchedRoute | None: if match: # Convert the matched groups to the correct types params = { - parameter_name[len("_numeric_") :] - if parameter_name.startswith("_numeric_") - else parameter_name: self.converter_mapping[parameter_name](value) + parameter_name.removeprefix("_numeric_"): self.converter_mapping[parameter_name](value) for parameter_name, value in match.groupdict().items() } return MatchedRoute(self.element, params, path) diff --git a/src/reactpy_router/routers.py b/src/reactpy_router/routers.py index fad94eb..8b3032b 100644 --- a/src/reactpy_router/routers.py +++ b/src/reactpy_router/routers.py @@ -4,12 +4,11 @@ from dataclasses import replace from logging import getLogger -from typing import TYPE_CHECKING, Any, Union, cast +from typing import TYPE_CHECKING, Any, cast -from reactpy import component, use_memo, use_state -from reactpy.backend.types import Connection, Location -from reactpy.core.hooks import ConnectionContext, use_connection -from reactpy.types import ComponentType, VdomDict +from reactpy import component, use_connection, use_memo, use_state +from reactpy.core.hooks import ConnectionContext +from reactpy.types import Component, Connection, Location, VdomDict from reactpy_router.components import History from reactpy_router.hooks import RouteState, _route_state_context @@ -18,8 +17,6 @@ if TYPE_CHECKING: from collections.abc import Iterator, Sequence - from reactpy.core.component import Component - from reactpy_router.types import CompiledRoute, MatchedRoute, Resolver, Route, Router __all__ = ["browser_router", "create_router"] @@ -63,7 +60,7 @@ def router( a custom routing engine.""" old_connection = use_connection() - location, set_location = use_state(cast(Union[Location, None], None)) + location, set_location = use_state(cast("Location | None", None)) resolvers = use_memo( lambda: tuple(map(resolver, _iter_routes(routes))), dependencies=(resolver, hash(routes)), @@ -105,11 +102,11 @@ def _add_route_key(match: MatchedRoute, key: str | int) -> Any: """Add a key to the VDOM or component on the current route, if it doesn't already have one.""" element = match.element if hasattr(element, "render") and not element.key: - element = cast(ComponentType, element) + element = cast("Component", element) element.key = key elif isinstance(element, dict) and not element.get("key", None): - element = cast(VdomDict, element) - element["key"] = key + element = cast("VdomDict", element) + element["attributes"]["key"] = key return match @@ -118,10 +115,10 @@ def _match_route( location: Location, ) -> MatchedRoute | None: for resolver in compiled_routes: - match = resolver.resolve(location.pathname) + match = resolver.resolve(location.path) if match is not None: return _add_route_key(match, resolver.key) - _logger.debug("No matching route found for %s", location.pathname) + _logger.debug("No matching route found for %s", location.path) return None diff --git a/src/reactpy_router/types.py b/src/reactpy_router/types.py index 755e244..e2032e7 100644 --- a/src/reactpy_router/types.py +++ b/src/reactpy_router/types.py @@ -2,18 +2,17 @@ from __future__ import annotations +from collections.abc import Callable from dataclasses import dataclass, field -from typing import TYPE_CHECKING, Any, Callable, TypedDict, TypeVar +from typing import TYPE_CHECKING, Any, Self, TypeAlias, TypedDict, TypeVar from reactpy.core.vdom import is_vdom -from typing_extensions import Protocol, Self, TypeAlias +from typing_extensions import Protocol if TYPE_CHECKING: from collections.abc import Sequence - from reactpy.backend.types import Location - from reactpy.core.component import Component - from reactpy.types import Key + from reactpy.types import Component, Key, Location ConversionFunc: TypeAlias = Callable[[str], Any] """A function that converts a string to a specific type.""" @@ -42,7 +41,11 @@ class Route: def __hash__(self) -> int: el = self.element - key = el["key"] if is_vdom(el) and "key" in el else getattr(el, "key", id(el)) + key = ( + el["attributes"]["key"] + if is_vdom(el) and "attributes" in el and "key" in el["attributes"] + else getattr(el, "key", id(el)) + ) return hash((self.path, key, self.routes)) diff --git a/tests/conftest.py b/tests/conftest.py index 3463e16..4355990 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -19,7 +19,9 @@ def pytest_addoption(parser) -> None: def pytest_sessionstart(session): """Rebuild the project before running the tests to get the latest JavaScript""" - subprocess.run(["hatch", "build", "--clean"], check=True) + env = os.environ.copy() + env.pop("HATCH_ENV_ACTIVE", None) + subprocess.run(["hatch", "build", "--clean"], check=True, env=env) subprocess.run(["playwright", "install", "chromium"], check=True) diff --git a/tests/test_resolver.py b/tests/test_resolver.py index cf1a17e..ee172db 100644 --- a/tests/test_resolver.py +++ b/tests/test_resolver.py @@ -10,7 +10,7 @@ def test_resolve_any(): resolver = ReactPyResolver(route("{404:any}", "Hello World")) - assert resolver.parse_path("{404:any}") == re.compile("^(?P<_numeric_404>.*)$") + assert resolver.parse_path("{404:any}") == re.compile(r"^(?P<_numeric_404>.*)$") assert resolver.converter_mapping == {"_numeric_404": str} assert resolver.resolve("/hello/world") == MatchedRoute( element="Hello World", params={"404": "/hello/world"}, path="/hello/world" @@ -22,7 +22,7 @@ class CustomResolver(ReactPyResolver): param_pattern = r"<(?P\w+)(?P:\w+)?>" resolver = CustomResolver(route("<404:any>", "Hello World")) - assert resolver.parse_path("<404:any>") == re.compile("^(?P<_numeric_404>.*)$") + assert resolver.parse_path("<404:any>") == re.compile(r"^(?P<_numeric_404>.*)$") assert resolver.converter_mapping == {"_numeric_404": str} assert resolver.resolve("/hello/world") == MatchedRoute( element="Hello World", params={"404": "/hello/world"}, path="/hello/world" @@ -31,7 +31,7 @@ class CustomResolver(ReactPyResolver): def test_parse_path(): resolver = ReactPyResolver(route("/", None)) - assert resolver.parse_path("/a/b/c") == re.compile("^/a/b/c$") + assert resolver.parse_path("/a/b/c") == re.compile(r"^/a/b/c$") assert resolver.converter_mapping == {} assert resolver.parse_path("/a/{b}/c") == re.compile(r"^/a/(?P[^/]+)/c$") @@ -61,7 +61,7 @@ def test_parse_path(): def test_parse_path_unkown_conversion(): resolver = ReactPyResolver(route("/", None)) - with pytest.raises(ValueError, match="Unknown conversion type 'unknown' in '/a/{b:unknown}/c'"): + with pytest.raises(ValueError, match=r"Unknown conversion type 'unknown' in '/a/{b:unknown}/c'"): resolver.parse_path("/a/{b:unknown}/c") diff --git a/tests/test_router.py b/tests/test_router.py index ddef544..f1922be 100644 --- a/tests/test_router.py +++ b/tests/test_router.py @@ -20,7 +20,7 @@ def make_location_check(path, *routes): @component def check_location(): - assert use_location().pathname == path + assert use_location().path == path return html.h1({"id": name}, path) return route(path, check_location(), *routes) @@ -45,13 +45,7 @@ def sample(): await display.goto("/missing") - try: - root_element = await display.root_element() - except AttributeError: - root_element = await display.page.wait_for_selector( - f"#display-{display._next_view_id}", # type: ignore - state="attached", - ) + root_element = await display.page.wait_for_selector("#app", state="attached") assert not await root_element.inner_html() @@ -99,8 +93,8 @@ def sample(): await display.show(sample) for link_selector in ["#root", "#a", "#b", "#c"]: - _link = await display.page.wait_for_selector(link_selector) - await _link.click(delay=CLICK_DELAY) + link_ = await display.page.wait_for_selector(link_selector) + await link_.click(delay=CLICK_DELAY) await display.page.wait_for_selector("#default") @@ -181,8 +175,8 @@ def sample(): link_selectors = ["#root", "#a", "#b", "#c"] for link_selector in link_selectors: - _link = await display.page.wait_for_selector(link_selector) - await _link.click(delay=CLICK_DELAY) + link_ = await display.page.wait_for_selector(link_selector) + await link_.click(delay=CLICK_DELAY) await display.page.wait_for_selector("#default") @@ -212,8 +206,8 @@ def sample(): selectors = ["#root", "#a", "#b", "#c", "#d", "#e", "#f"] for link_selector in selectors: - _link = await display.page.wait_for_selector(link_selector) - await _link.click(delay=CLICK_DELAY) + link_ = await display.page.wait_for_selector(link_selector) + await link_.click(delay=CLICK_DELAY) await display.page.wait_for_selector("#default") @@ -240,8 +234,8 @@ def sample(): await display.show(sample) await display.page.wait_for_selector("#root") - _link = await display.page.wait_for_selector("#root") - await _link.click(delay=CLICK_DELAY) + link_ = await display.page.wait_for_selector("#root") + await link_.click(delay=CLICK_DELAY) await display.page.wait_for_selector("#success") @@ -252,8 +246,8 @@ def sample(): await display.show(sample) - _link = await display.page.wait_for_selector("#root") - assert "class1" in await _link.get_attribute("class") + link_ = await display.page.wait_for_selector("#root") + assert "class1" in await link_.get_attribute("class") async def test_link_href(display: DisplayFixture): @@ -263,8 +257,8 @@ def sample(): await display.show(sample) - _link = await display.page.wait_for_selector("#root") - assert "/a" in await _link.get_attribute("href") + link_ = await display.page.wait_for_selector("#root") + assert "/a" in await link_.get_attribute("href") async def test_ctrl_click(display: DisplayFixture, browser: Browser): @@ -277,8 +271,8 @@ def sample(): await display.show(sample) - _link = await display.page.wait_for_selector("#root") - await _link.click(delay=CLICK_DELAY, modifiers=["Control"]) + link_ = await display.page.wait_for_selector("#root") + await link_.click(delay=CLICK_DELAY, modifiers=["Control"]) browser_context = browser.contexts[0] if len(browser_context.pages) == 1: new_page: Page = await browser_context.wait_for_event("page") @@ -305,8 +299,8 @@ def sample(): ) await display.show(sample) - _button = await display.page.wait_for_selector("button") - await _button.click(delay=CLICK_DELAY) + button = await display.page.wait_for_selector("button") + await button.click(delay=CLICK_DELAY) await display.page.wait_for_selector("#a") await asyncio.sleep(CLICK_DELAY / 1000) await display.page.go_back() @@ -332,10 +326,10 @@ def sample(): ) await display.show(sample) - _button = await display.page.wait_for_selector("#nav-a") - await _button.click(delay=CLICK_DELAY) - _button = await display.page.wait_for_selector("#nav-b") - await _button.click(delay=CLICK_DELAY) + button = await display.page.wait_for_selector("#nav-a") + await button.click(delay=CLICK_DELAY) + button = await display.page.wait_for_selector("#nav-b") + await button.click(delay=CLICK_DELAY) await display.page.wait_for_selector("#b") await asyncio.sleep(CLICK_DELAY / 1000) await display.page.go_back() @@ -360,10 +354,10 @@ def sample(): ) await display.show(sample) - _button = await display.page.wait_for_selector("#root-a") - await _button.click(delay=CLICK_DELAY) - _button = await display.page.wait_for_selector("#nav-a") - await _button.click(delay=CLICK_DELAY) + button = await display.page.wait_for_selector("#root-a") + await button.click(delay=CLICK_DELAY) + button = await display.page.wait_for_selector("#nav-a") + await button.click(delay=CLICK_DELAY) await asyncio.sleep(CLICK_DELAY / 1000) await display.page.wait_for_selector("#nav-a") await asyncio.sleep(CLICK_DELAY / 1000)