diff --git a/.copier-answers.yml b/.copier-answers.yml new file mode 100644 index 0000000..57b40f1 --- /dev/null +++ b/.copier-answers.yml @@ -0,0 +1,15 @@ +# Changes here will be overwritten by Copier; NEVER EDIT MANUALLY +_commit: v4.2.3 +_src_path: https://github.com/jupyterlab/extension-template +author_email: aflewandowski@alaska.edu +author_name: Alex Lewandowski +has_binder: true +has_settings: false +kind: frontend +labextension_name: jlab-jbook-chapter-navigation +project_short_description: A JupyterLab extension that mimics jupyter-book chapter + navigation on an un-built, cloned jupyter book in JupyterLab. +python_name: jlab_jbook_chapter_navigation +repository: https://github.com/Alex-Lewandowski/jupyterlab-jbook-chapter-navigation +test: true + diff --git a/.github/workflows/binder-on-pr.yml b/.github/workflows/binder-on-pr.yml new file mode 100644 index 0000000..87e9cd2 --- /dev/null +++ b/.github/workflows/binder-on-pr.yml @@ -0,0 +1,14 @@ +name: Binder Badge +on: + pull_request_target: + types: [opened] + +jobs: + binder: + runs-on: ubuntu-latest + permissions: + pull-requests: write + steps: + - uses: jupyterlab/maintainer-tools/.github/actions/binder-link@v1 + with: + github_token: ${{ secrets.github_token }} diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 0000000..5444379 --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,149 @@ +name: Build + +on: + push: + branches: main + pull_request: + branches: '*' + +jobs: + build: + runs-on: ubuntu-latest + + steps: + - name: Checkout + uses: actions/checkout@v3 + + - name: Base Setup + uses: jupyterlab/maintainer-tools/.github/actions/base-setup@v1 + + - name: Install dependencies + run: python -m pip install -U "jupyterlab>=4.0.0,<5" + + - name: Lint the extension + run: | + set -eux + jlpm + jlpm run lint:check + + - name: Test the extension + run: | + set -eux + jlpm run test + + - name: Build the extension + run: | + set -eux + python -m pip install .[test] + + jupyter labextension list + jupyter labextension list 2>&1 | grep -ie "jlab-jbook-chapter-navigation.*OK" + python -m jupyterlab.browser_check + + - name: Package the extension + run: | + set -eux + + pip install build + python -m build + pip uninstall -y "jlab_jbook_chapter_navigation" jupyterlab + + - name: Upload extension packages + uses: actions/upload-artifact@v3 + with: + name: extension-artifacts + path: dist/jlab_jbook_chapter_navigation* + if-no-files-found: error + + test_isolated: + needs: build + runs-on: ubuntu-latest + + steps: + - name: Install Python + uses: actions/setup-python@v4 + with: + python-version: '3.9' + architecture: 'x64' + - uses: actions/download-artifact@v3 + with: + name: extension-artifacts + - name: Install and Test + run: | + set -eux + # Remove NodeJS, twice to take care of system and locally installed node versions. + sudo rm -rf $(which node) + sudo rm -rf $(which node) + + pip install "jupyterlab>=4.0.0,<5" jlab_jbook_chapter_navigation*.whl + + + jupyter labextension list + jupyter labextension list 2>&1 | grep -ie "jlab-jbook-chapter-navigation.*OK" + python -m jupyterlab.browser_check --no-browser-test + + integration-tests: + name: Integration tests + needs: build + runs-on: ubuntu-latest + + env: + PLAYWRIGHT_BROWSERS_PATH: ${{ github.workspace }}/pw-browsers + + steps: + - name: Checkout + uses: actions/checkout@v3 + + - name: Base Setup + uses: jupyterlab/maintainer-tools/.github/actions/base-setup@v1 + + - name: Download extension package + uses: actions/download-artifact@v3 + with: + name: extension-artifacts + + - name: Install the extension + run: | + set -eux + python -m pip install "jupyterlab>=4.0.0,<5" jlab_jbook_chapter_navigation*.whl + + - name: Install dependencies + working-directory: ui-tests + env: + YARN_ENABLE_IMMUTABLE_INSTALLS: 0 + PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD: 1 + run: jlpm install + + - name: Set up browser cache + uses: actions/cache@v3 + with: + path: | + ${{ github.workspace }}/pw-browsers + key: ${{ runner.os }}-${{ hashFiles('ui-tests/yarn.lock') }} + + - name: Install browser + run: jlpm playwright install chromium + working-directory: ui-tests + + - name: Execute integration tests + working-directory: ui-tests + run: | + jlpm playwright test + + - name: Upload Playwright Test report + if: always() + uses: actions/upload-artifact@v3 + with: + name: jlab_jbook_chapter_navigation-playwright-tests + path: | + ui-tests/test-results + ui-tests/playwright-report + + check_links: + name: Check Links + runs-on: ubuntu-latest + timeout-minutes: 15 + steps: + - uses: actions/checkout@v3 + - uses: jupyterlab/maintainer-tools/.github/actions/base-setup@v1 + - uses: jupyterlab/maintainer-tools/.github/actions/check-links@v1 diff --git a/.github/workflows/check-release.yml b/.github/workflows/check-release.yml new file mode 100644 index 0000000..33ee3ab --- /dev/null +++ b/.github/workflows/check-release.yml @@ -0,0 +1,26 @@ +name: Check Release +on: + push: + branches: ["main"] + pull_request: + branches: ["*"] + +jobs: + check_release: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v3 + - name: Base Setup + uses: jupyterlab/maintainer-tools/.github/actions/base-setup@v1 + - name: Check Release + uses: jupyter-server/jupyter_releaser/.github/actions/check-release@v2 + with: + + token: ${{ secrets.GITHUB_TOKEN }} + + - name: Upload Distributions + uses: actions/upload-artifact@v3 + with: + name: jlab_jbook_chapter_navigation-releaser-dist-${{ github.run_number }} + path: .jupyter_releaser_checkout/dist diff --git a/.github/workflows/enforce-label.yml b/.github/workflows/enforce-label.yml new file mode 100644 index 0000000..725feab --- /dev/null +++ b/.github/workflows/enforce-label.yml @@ -0,0 +1,13 @@ +name: Enforce PR label + +on: + pull_request: + types: [labeled, unlabeled, opened, edited, synchronize] +jobs: + enforce-label: + runs-on: ubuntu-latest + permissions: + pull-requests: write + steps: + - name: enforce-triage-label + uses: jupyterlab/maintainer-tools/.github/actions/enforce-label@v1 diff --git a/.github/workflows/prep-release.yml b/.github/workflows/prep-release.yml new file mode 100644 index 0000000..6f09281 --- /dev/null +++ b/.github/workflows/prep-release.yml @@ -0,0 +1,41 @@ +name: "Step 1: Prep Release" +on: + workflow_dispatch: + inputs: + version_spec: + description: "New Version Specifier" + default: "next" + required: false + branch: + description: "The branch to target" + required: false + post_version_spec: + description: "Post Version Specifier" + required: false + since: + description: "Use PRs with activity since this date or git reference" + required: false + since_last_stable: + description: "Use PRs with activity since the last stable git tag" + required: false + type: boolean +jobs: + prep_release: + runs-on: ubuntu-latest + steps: + - uses: jupyterlab/maintainer-tools/.github/actions/base-setup@v1 + + - name: Prep Release + id: prep-release + uses: jupyter-server/jupyter_releaser/.github/actions/prep-release@v2 + with: + token: ${{ secrets.ADMIN_GITHUB_TOKEN }} + version_spec: ${{ github.event.inputs.version_spec }} + post_version_spec: ${{ github.event.inputs.post_version_spec }} + branch: ${{ github.event.inputs.branch }} + since: ${{ github.event.inputs.since }} + since_last_stable: ${{ github.event.inputs.since_last_stable }} + + - name: "** Next Step **" + run: | + echo "Optional): Review Draft Release: ${{ steps.prep-release.outputs.release_url }}" diff --git a/.github/workflows/publish-release.yml b/.github/workflows/publish-release.yml new file mode 100644 index 0000000..cf6d905 --- /dev/null +++ b/.github/workflows/publish-release.yml @@ -0,0 +1,57 @@ +name: "Step 2: Publish Release" +on: + workflow_dispatch: + inputs: + branch: + description: "The target branch" + required: false + release_url: + description: "The URL of the draft GitHub release" + required: false + steps_to_skip: + description: "Comma separated list of steps to skip" + required: false + +jobs: + publish_release: + runs-on: ubuntu-latest + permissions: + # This is useful if you want to use PyPI trusted publisher + # and NPM provenance + id-token: write + steps: + - uses: jupyterlab/maintainer-tools/.github/actions/base-setup@v1 + + - name: Populate Release + id: populate-release + uses: jupyter-server/jupyter_releaser/.github/actions/populate-release@v2 + with: + token: ${{ secrets.ADMIN_GITHUB_TOKEN }} + branch: ${{ github.event.inputs.branch }} + release_url: ${{ github.event.inputs.release_url }} + steps_to_skip: ${{ github.event.inputs.steps_to_skip }} + + - name: Finalize Release + id: finalize-release + env: + # The following are needed if you use legacy PyPI set up + # PYPI_TOKEN: ${{ secrets.PYPI_TOKEN }} + # PYPI_TOKEN_MAP: ${{ secrets.PYPI_TOKEN_MAP }} + # TWINE_USERNAME: __token__ + NPM_TOKEN: ${{ secrets.NPM_TOKEN }} + uses: jupyter-server/jupyter_releaser/.github/actions/finalize-release@v2 + with: + token: ${{ secrets.ADMIN_GITHUB_TOKEN }} + release_url: ${{ steps.populate-release.outputs.release_url }} + + - name: "** Next Step **" + if: ${{ success() }} + run: | + echo "Verify the final release" + echo ${{ steps.finalize-release.outputs.release_url }} + + - name: "** Failure Message **" + if: ${{ failure() }} + run: | + echo "Failed to Publish the Draft Release Url:" + echo ${{ steps.populate-release.outputs.release_url }} diff --git a/.github/workflows/update-integration-tests.yml b/.github/workflows/update-integration-tests.yml new file mode 100644 index 0000000..7ea5a66 --- /dev/null +++ b/.github/workflows/update-integration-tests.yml @@ -0,0 +1,50 @@ +name: Update Playwright Snapshots + +on: + issue_comment: + types: [created, edited] + +permissions: + contents: write + pull-requests: write + +jobs: + update-snapshots: + if: ${{ github.event.issue.pull_request && contains(github.event.comment.body, 'please update snapshots') }} + runs-on: ubuntu-latest + + steps: + - name: React to the triggering comment + run: | + gh api repos/${{ github.repository }}/issues/comments/${{ github.event.comment.id }}/reactions --raw-field 'content=+1' + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + - name: Checkout + uses: actions/checkout@v4 + with: + token: ${{ secrets.GITHUB_TOKEN }} + + - name: Checkout the branch from the PR that triggered the job + run: gh pr checkout ${{ github.event.issue.number }} + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + - name: Base Setup + uses: jupyterlab/maintainer-tools/.github/actions/base-setup@v1 + + - name: Install dependencies + run: python -m pip install -U "jupyterlab>=4.0.0,<5" + + - name: Install extension + run: | + set -eux + jlpm + python -m pip install . + + - uses: jupyterlab/maintainer-tools/.github/actions/update-snapshots@v1 + with: + github_token: ${{ secrets.GITHUB_TOKEN }} + # Playwright knows how to start JupyterLab server + start_server_script: 'null' + test_folder: ui-tests diff --git a/.gitignore b/.gitignore index 8a30d25..6378586 100644 --- a/.gitignore +++ b/.gitignore @@ -1,398 +1,125 @@ -## Ignore Visual Studio temporary files, build results, and -## files generated by popular Visual Studio add-ons. -## -## Get latest from https://github.com/github/gitignore/blob/main/VisualStudio.gitignore - -# User-specific files -*.rsuser -*.suo -*.user -*.userosscache -*.sln.docstates - -# User-specific files (MonoDevelop/Xamarin Studio) -*.userprefs - -# Mono auto generated files -mono_crash.* - -# Build results -[Dd]ebug/ -[Dd]ebugPublic/ -[Rr]elease/ -[Rr]eleases/ -x64/ -x86/ -[Ww][Ii][Nn]32/ -[Aa][Rr][Mm]/ -[Aa][Rr][Mm]64/ -bld/ -[Bb]in/ -[Oo]bj/ -[Ll]og/ -[Ll]ogs/ - -# Visual Studio 2015/2017 cache/options directory -.vs/ -# Uncomment if you have tasks that create the project's static files in wwwroot -#wwwroot/ - -# Visual Studio 2017 auto generated files -Generated\ Files/ - -# MSTest test Results -[Tt]est[Rr]esult*/ -[Bb]uild[Ll]og.* - -# NUnit -*.VisualState.xml -TestResult.xml -nunit-*.xml - -# Build Results of an ATL Project -[Dd]ebugPS/ -[Rr]eleasePS/ -dlldata.c - -# Benchmark Results -BenchmarkDotNet.Artifacts/ - -# .NET Core -project.lock.json -project.fragment.lock.json -artifacts/ - -# ASP.NET Scaffolding -ScaffoldingReadMe.txt - -# StyleCop -StyleCopReport.xml - -# Files built by Visual Studio -*_i.c -*_p.c -*_h.h -*.ilk -*.meta -*.obj -*.iobj -*.pch -*.pdb -*.ipdb -*.pgc -*.pgd -*.rsp -*.sbr -*.tlb -*.tli -*.tlh -*.tmp -*.tmp_proj -*_wpftmp.csproj -*.log -*.tlog -*.vspscc -*.vssscc -.builds -*.pidb -*.svclog -*.scc - -# Chutzpah Test files -_Chutzpah* - -# Visual C++ cache files -ipch/ -*.aps -*.ncb -*.opendb -*.opensdf -*.sdf -*.cachefile -*.VC.db -*.VC.VC.opendb - -# Visual Studio profiler -*.psess -*.vsp -*.vspx -*.sap - -# Visual Studio Trace Files -*.e2e - -# TFS 2012 Local Workspace -$tf/ - -# Guidance Automation Toolkit -*.gpState - -# ReSharper is a .NET coding add-in -_ReSharper*/ -*.[Rr]e[Ss]harper -*.DotSettings.user - -# TeamCity is a build add-in -_TeamCity* - -# DotCover is a Code Coverage Tool -*.dotCover - -# AxoCover is a Code Coverage Tool -.axoCover/* -!.axoCover/settings.json - -# Coverlet is a free, cross platform Code Coverage Tool -coverage*.json -coverage*.xml -coverage*.info - -# Visual Studio code coverage results -*.coverage -*.coveragexml - -# NCrunch -_NCrunch_* -.*crunch*.local.xml -nCrunchTemp_* - -# MightyMoose -*.mm.* -AutoTest.Net/ - -# Web workbench (sass) -.sass-cache/ - -# Installshield output folder -[Ee]xpress/ - -# DocProject is a documentation generator add-in -DocProject/buildhelp/ -DocProject/Help/*.HxT -DocProject/Help/*.HxC -DocProject/Help/*.hhc -DocProject/Help/*.hhk -DocProject/Help/*.hhp -DocProject/Help/Html2 -DocProject/Help/html - -# Click-Once directory -publish/ - -# Publish Web Output -*.[Pp]ublish.xml -*.azurePubxml -# Note: Comment the next line if you want to checkin your web deploy settings, -# but database connection strings (with potential passwords) will be unencrypted -*.pubxml -*.publishproj - -# Microsoft Azure Web App publish settings. Comment the next line if you want to -# checkin your Azure Web App publish settings, but sensitive information contained -# in these scripts will be unencrypted -PublishScripts/ - -# NuGet Packages -*.nupkg -# NuGet Symbol Packages -*.snupkg -# The packages folder can be ignored because of Package Restore -**/[Pp]ackages/* -# except build/, which is used as an MSBuild target. -!**/[Pp]ackages/build/ -# Uncomment if necessary however generally it will be regenerated when needed -#!**/[Pp]ackages/repositories.config -# NuGet v3's project.json files produces more ignorable files -*.nuget.props -*.nuget.targets - -# Microsoft Azure Build Output -csx/ -*.build.csdef - -# Microsoft Azure Emulator -ecf/ -rcf/ - -# Windows Store app package directories and files -AppPackages/ -BundleArtifacts/ -Package.StoreAssociation.xml -_pkginfo.txt -*.appx -*.appxbundle -*.appxupload - -# Visual Studio cache files -# files ending in .cache can be ignored -*.[Cc]ache -# but keep track of directories ending in .cache -!?*.[Cc]ache/ - -# Others -ClientBin/ -~$* -*~ -*.dbmdl -*.dbproj.schemaview -*.jfm -*.pfx -*.publishsettings -orleans.codegen.cs - -# Including strong name files can present a security risk -# (https://github.com/github/gitignore/pull/2483#issue-259490424) -#*.snk - -# Since there are multiple workflows, uncomment next line to ignore bower_components -# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) -#bower_components/ - -# RIA/Silverlight projects -Generated_Code/ - -# Backup & report files from converting an old project file -# to a newer Visual Studio version. Backup files are not needed, -# because we have git ;-) -_UpgradeReport_Files/ -Backup*/ -UpgradeLog*.XML -UpgradeLog*.htm -ServiceFabricBackup/ -*.rptproj.bak - -# SQL Server files -*.mdf -*.ldf -*.ndf - -# Business Intelligence projects -*.rdl.data -*.bim.layout -*.bim_*.settings -*.rptproj.rsuser -*- [Bb]ackup.rdl -*- [Bb]ackup ([0-9]).rdl -*- [Bb]ackup ([0-9][0-9]).rdl - -# Microsoft Fakes -FakesAssemblies/ - -# GhostDoc plugin setting file -*.GhostDoc.xml - -# Node.js Tools for Visual Studio -.ntvs_analysis.dat +*.bundle.* +lib/ node_modules/ - -# Visual Studio 6 build log -*.plg - -# Visual Studio 6 workspace options file -*.opt - -# Visual Studio 6 auto-generated workspace file (contains which files were open etc.) -*.vbw - -# Visual Studio 6 auto-generated project file (contains which files were open etc.) -*.vbp - -# Visual Studio 6 workspace and project file (working project files containing files to include in project) -*.dsw -*.dsp - -# Visual Studio 6 technical files -*.ncb -*.aps - -# Visual Studio LightSwitch build output -**/*.HTMLClient/GeneratedArtifacts -**/*.DesktopClient/GeneratedArtifacts -**/*.DesktopClient/ModelManifest.xml -**/*.Server/GeneratedArtifacts -**/*.Server/ModelManifest.xml -_Pvt_Extensions - -# Paket dependency manager -.paket/paket.exe -paket-files/ - -# FAKE - F# Make -.fake/ - -# CodeRush personal settings -.cr/personal - -# Python Tools for Visual Studio (PTVS) +*.log +.eslintcache +.stylelintcache +*.egg-info/ +.ipynb_checkpoints +*.tsbuildinfo +jlab_jbook_chapter_navigation/labextension +# Version file is handled by hatchling +jlab_jbook_chapter_navigation/_version.py + +# Integration tests +ui-tests/test-results/ +ui-tests/playwright-report/ + +# Created by https://www.gitignore.io/api/python +# Edit at https://www.gitignore.io/?templates=python + +### Python ### +# Byte-compiled / optimized / DLL files __pycache__/ -*.pyc - -# Cake - Uncomment if you are using it -# tools/** -# !tools/packages.config - -# Tabs Studio -*.tss - -# Telerik's JustMock configuration file -*.jmconfig - -# BizTalk build output -*.btp.cs -*.btm.cs -*.odx.cs -*.xsd.cs - -# OpenCover UI analysis results -OpenCover/ - -# Azure Stream Analytics local run output -ASALocalRun/ - -# MSBuild Binary and Structured Log -*.binlog - -# NVidia Nsight GPU debugger configuration file -*.nvuser - -# MFractors (Xamarin productivity tool) working folder -.mfractor/ - -# Local History for Visual Studio -.localhistory/ - -# Visual Studio History (VSHistory) files -.vshistory/ - -# BeatPulse healthcheck temp database -healthchecksdb - -# Backup folder for Package Reference Convert tool in Visual Studio 2017 -MigrationBackup/ - -# Ionide (cross platform F# VS Code tools) working folder -.ionide/ - -# Fody - auto-generated XML schema -FodyWeavers.xsd - -# VS Code files for those working on multiple tools -.vscode/* -!.vscode/settings.json -!.vscode/tasks.json -!.vscode/launch.json -!.vscode/extensions.json -*.code-workspace - -# Local History for Visual Studio Code -.history/ - -# Windows Installer files from build outputs -*.cab -*.msi -*.msix -*.msm -*.msp - -# JetBrains Rider -*.sln.iml +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +pip-wheel-metadata/ +share/python-wheels/ +.installed.cfg +*.egg +MANIFEST + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.nox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage/ +coverage.xml +*.cover +.hypothesis/ +.pytest_cache/ + +# Translations +*.mo +*.pot + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +target/ + +# pyenv +.python-version + +# celery beat schedule file +celerybeat-schedule + +# SageMath parsed files +*.sage.py + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# Mr Developer +.mr.developer.cfg +.project +.pydevproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ +.dmypy.json +dmypy.json + +# Pyre type checker +.pyre/ + +# End of https://www.gitignore.io/api/python + +# OSX files +.DS_Store + +# Yarn cache +.yarn/ diff --git a/.prettierignore b/.prettierignore new file mode 100644 index 0000000..bf6056f --- /dev/null +++ b/.prettierignore @@ -0,0 +1,6 @@ +node_modules +**/node_modules +**/lib +**/package.json +!/package.json +jlab_jbook_chapter_navigation diff --git a/.yarnrc.yml b/.yarnrc.yml new file mode 100644 index 0000000..3186f3f --- /dev/null +++ b/.yarnrc.yml @@ -0,0 +1 @@ +nodeLinker: node-modules diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..2d352af --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,5 @@ +# Changelog + + + + diff --git a/README.md b/README.md index b16dcee..4f8d685 100644 --- a/README.md +++ b/README.md @@ -1 +1,96 @@ -# jupyterlab-jbook-chapter-navigation \ No newline at end of file +# jlab_jbook_chapter_navigation + +[![Github Actions Status](https://github.com/Alex-Lewandowski/jupyterlab-jbook-chapter-navigation/workflows/Build/badge.svg)](https://github.com/Alex-Lewandowski/jupyterlab-jbook-chapter-navigation/actions/workflows/build.yml)[![Binder](https://mybinder.org/badge_logo.svg)](https://mybinder.org/v2/gh/Alex-Lewandowski/jupyterlab-jbook-chapter-navigation/main?urlpath=lab) +A JupyterLab extension that mimics jupyter-book chapter navigation on an un-built, cloned jupyter book in JupyterLab. + +## Requirements + +- JupyterLab >= 4.0.0 + +## Install + +To install the extension, execute: + +```bash +pip install jlab_jbook_chapter_navigation +``` + +## Uninstall + +To remove the extension, execute: + +```bash +pip uninstall jlab_jbook_chapter_navigation +``` + +## Contributing + +### Development install + +Note: You will need NodeJS to build the extension package. + +The `jlpm` command is JupyterLab's pinned version of +[yarn](https://yarnpkg.com/) that is installed with JupyterLab. You may use +`yarn` or `npm` in lieu of `jlpm` below. + +```bash +# Clone the repo to your local environment +# Change directory to the jlab_jbook_chapter_navigation directory +# Install package in development mode +pip install -e "." +# Link your development version of the extension with JupyterLab +jupyter labextension develop . --overwrite +# Rebuild extension Typescript source after making changes +jlpm build +``` + +You can watch the source directory and run JupyterLab at the same time in different terminals to watch for changes in the extension's source and automatically rebuild the extension. + +```bash +# Watch the source directory in one terminal, automatically rebuilding when needed +jlpm watch +# Run JupyterLab in another terminal +jupyter lab +``` + +With the watch command running, every saved change will immediately be built locally and available in your running JupyterLab. Refresh JupyterLab to load the change in your browser (you may need to wait several seconds for the extension to be rebuilt). + +By default, the `jlpm build` command generates the source maps for this extension to make it easier to debug using the browser dev tools. To also generate source maps for the JupyterLab core extensions, you can run the following command: + +```bash +jupyter lab build --minimize=False +``` + +### Development uninstall + +```bash +pip uninstall jlab_jbook_chapter_navigation +``` + +In development mode, you will also need to remove the symlink created by `jupyter labextension develop` +command. To find its location, you can run `jupyter labextension list` to figure out where the `labextensions` +folder is located. Then you can remove the symlink named `jlab-jbook-chapter-navigation` within that folder. + +### Testing the extension + +#### Frontend tests + +This extension is using [Jest](https://jestjs.io/) for JavaScript code testing. + +To execute them, execute: + +```sh +jlpm +jlpm test +``` + +#### Integration tests + +This extension uses [Playwright](https://playwright.dev/docs/intro) for the integration tests (aka user level tests). +More precisely, the JupyterLab helper [Galata](https://github.com/jupyterlab/jupyterlab/tree/master/galata) is used to handle testing the extension in JupyterLab. + +More information are provided within the [ui-tests](./ui-tests/README.md) README. + +### Packaging the extension + +See [RELEASE](RELEASE.md) diff --git a/RELEASE.md b/RELEASE.md new file mode 100644 index 0000000..7e4dd14 --- /dev/null +++ b/RELEASE.md @@ -0,0 +1,111 @@ +# Making a new release of jlab_jbook_chapter_navigation + +The extension can be published to `PyPI` and `npm` manually or using the [Jupyter Releaser](https://github.com/jupyter-server/jupyter_releaser). + +## Manual release + +### Python package + +This extension can be distributed as Python packages. All of the Python +packaging instructions are in the `pyproject.toml` file to wrap your extension in a +Python package. Before generating a package, you first need to install some tools: + +```bash +pip install build twine hatch +``` + +Bump the version using `hatch`. By default this will create a tag. +See the docs on [hatch-nodejs-version](https://github.com/agoose77/hatch-nodejs-version#semver) for details. + +```bash +hatch version +``` + +Make sure to clean up all the development files before building the package: + +```bash +jlpm clean:all +``` + +You could also clean up the local git repository: + +```bash +git clean -dfX +``` + +To create a Python source package (`.tar.gz`) and the binary package (`.whl`) in the `dist/` directory, do: + +```bash +python -m build +``` + +> `python setup.py sdist bdist_wheel` is deprecated and will not work for this package. + +Then to upload the package to PyPI, do: + +```bash +twine upload dist/* +``` + +### NPM package + +To publish the frontend part of the extension as a NPM package, do: + +```bash +npm login +npm publish --access public +``` + +## Automated releases with the Jupyter Releaser + +The extension repository should already be compatible with the Jupyter Releaser. + +Check out the [workflow documentation](https://jupyter-releaser.readthedocs.io/en/latest/get_started/making_release_from_repo.html) for more information. + +Here is a summary of the steps to cut a new release: + +- Add tokens to the [Github Secrets](https://docs.github.com/en/actions/security-guides/encrypted-secrets) in the repository: + - `ADMIN_GITHUB_TOKEN` (with "public_repo" and "repo:status" permissions); see the [documentation](https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/creating-a-personal-access-token) + - `NPM_TOKEN` (with "automation" permission); see the [documentation](https://docs.npmjs.com/creating-and-viewing-access-tokens) +- Set up PyPI + +
Using PyPI trusted publisher (modern way) + +- Set up your PyPI project by [adding a trusted publisher](https://docs.pypi.org/trusted-publishers/adding-a-publisher/) + - The _workflow name_ is `publish-release.yml` and the _environment_ should be left blank. +- Ensure the publish release job as `permissions`: `id-token : write` (see the [documentation](https://docs.pypi.org/trusted-publishers/using-a-publisher/)) + +
+ +
Using PyPI token (legacy way) + +- If the repo generates PyPI release(s), create a scoped PyPI [token](https://packaging.python.org/guides/publishing-package-distribution-releases-using-github-actions-ci-cd-workflows/#saving-credentials-on-github). We recommend using a scoped token for security reasons. + +- You can store the token as `PYPI_TOKEN` in your fork's `Secrets`. + + - Advanced usage: if you are releasing multiple repos, you can create a secret named `PYPI_TOKEN_MAP` instead of `PYPI_TOKEN` that is formatted as follows: + + ```text + owner1/repo1,token1 + owner2/repo2,token2 + ``` + + If you have multiple Python packages in the same repository, you can point to them as follows: + + ```text + owner1/repo1/path/to/package1,token1 + owner1/repo1/path/to/package2,token2 + ``` + +
+ +- Go to the Actions panel +- Run the "Step 1: Prep Release" workflow +- Check the draft changelog +- Run the "Step 2: Publish Release" workflow + +## Publishing to `conda-forge` + +If the package is not on conda forge yet, check the documentation to learn how to add it: https://conda-forge.org/docs/maintainer/adding_pkgs.html + +Otherwise a bot should pick up the new version publish to PyPI, and open a new PR on the feedstock repository automatically. diff --git a/babel.config.js b/babel.config.js new file mode 100644 index 0000000..8b5c764 --- /dev/null +++ b/babel.config.js @@ -0,0 +1 @@ +module.exports = require('@jupyterlab/testutils/lib/babel.config'); diff --git a/binder/environment.yml b/binder/environment.yml new file mode 100644 index 0000000..c11efb2 --- /dev/null +++ b/binder/environment.yml @@ -0,0 +1,21 @@ +# a mybinder.org-ready environment for demoing jlab_jbook_chapter_navigation +# this environment may also be used locally on Linux/MacOS/Windows, e.g. +# +# conda env update --file binder/environment.yml +# conda activate jlab-jbook-chapter-navigation-demo +# +name: jlab-jbook-chapter-navigation-demo + +channels: + - conda-forge + +dependencies: + # runtime dependencies + - python >=3.10,<3.11.0a0 + - jupyterlab >=4.0.0,<5 + # labextension build dependencies + - nodejs >=18,<19 + - pip + - wheel + # additional packages for demos + # - ipywidgets diff --git a/binder/postBuild b/binder/postBuild new file mode 100755 index 0000000..97df460 --- /dev/null +++ b/binder/postBuild @@ -0,0 +1,47 @@ +#!/usr/bin/env python3 +""" perform a development install of jlab_jbook_chapter_navigation + + On Binder, this will run _after_ the environment has been fully created from + the environment.yml in this directory. + + This script should also run locally on Linux/MacOS/Windows: + + python3 binder/postBuild +""" +import subprocess +import sys +from pathlib import Path + + +ROOT = Path.cwd() + +def _(*args, **kwargs): + """ Run a command, echoing the args + + fails hard if something goes wrong + """ + print("\n\t", " ".join(args), "\n") + return_code = subprocess.call(args, **kwargs) + if return_code != 0: + print("\nERROR", return_code, " ".join(args)) + sys.exit(return_code) + +# verify the environment is self-consistent before even starting +_(sys.executable, "-m", "pip", "check") + +# install the labextension +_(sys.executable, "-m", "pip", "install", "-e", ".") +_(sys.executable, "-m", "jupyter", "labextension", "develop", "--overwrite", ".") + +# verify the environment the extension didn't break anything +_(sys.executable, "-m", "pip", "check") + +# list the extensions +_("jupyter", "server", "extension", "list") + +# initially list installed extensions to determine if there are any surprises +_("jupyter", "labextension", "list") + + +print("JupyterLab with jlab_jbook_chapter_navigation is ready to run with:\n") +print("\tjupyter lab\n") diff --git a/install.json b/install.json new file mode 100644 index 0000000..a5e65c7 --- /dev/null +++ b/install.json @@ -0,0 +1,5 @@ +{ + "packageManager": "python", + "packageName": "jlab_jbook_chapter_navigation", + "uninstallInstructions": "Use your Python package manager (pip, conda, etc.) to uninstall the package jlab_jbook_chapter_navigation" +} diff --git a/jest.config.js b/jest.config.js new file mode 100644 index 0000000..b0471e6 --- /dev/null +++ b/jest.config.js @@ -0,0 +1,28 @@ +const jestJupyterLab = require('@jupyterlab/testutils/lib/jest-config'); + +const esModules = [ + '@codemirror', + '@jupyter/ydoc', + '@jupyterlab/', + 'lib0', + 'nanoid', + 'vscode-ws-jsonrpc', + 'y-protocols', + 'y-websocket', + 'yjs' +].join('|'); + +const baseConfig = jestJupyterLab(__dirname); + +module.exports = { + ...baseConfig, + automock: false, + collectCoverageFrom: [ + 'src/**/*.{ts,tsx}', + '!src/**/*.d.ts', + '!src/**/.ipynb_checkpoints/*' + ], + coverageReporters: ['lcov', 'text'], + testRegex: 'src/.*/.*.spec.ts[x]?$', + transformIgnorePatterns: [`/node_modules/(?!${esModules}).+`] +}; diff --git a/jlab_jbook_chapter_navigation/__init__.py b/jlab_jbook_chapter_navigation/__init__.py new file mode 100644 index 0000000..3cb73ac --- /dev/null +++ b/jlab_jbook_chapter_navigation/__init__.py @@ -0,0 +1,16 @@ +try: + from ._version import __version__ +except ImportError: + # Fallback when using the package in dev mode without installing + # in editable mode with pip. It is highly recommended to install + # the package from a stable release or in editable mode: https://pip.pypa.io/en/stable/topics/local-project-installs/#editable-installs + import warnings + warnings.warn("Importing 'jlab_jbook_chapter_navigation' outside a proper installation.") + __version__ = "dev" + + +def _jupyter_labextension_paths(): + return [{ + "src": "labextension", + "dest": "jlab-jbook-chapter-navigation" + }] diff --git a/package.json b/package.json new file mode 100644 index 0000000..6158f9f --- /dev/null +++ b/package.json @@ -0,0 +1,192 @@ +{ + "name": "jlab-jbook-chapter-navigation", + "version": "0.1.0", + "description": "A JupyterLab extension that mimics jupyter-book chapter navigation on an un-built, cloned jupyter book in JupyterLab.", + "keywords": [ + "jupyter", + "jupyterlab", + "jupyterlab-extension" + ], + "homepage": "https://github.com/Alex-Lewandowski/jupyterlab-jbook-chapter-navigation", + "bugs": { + "url": "https://github.com/Alex-Lewandowski/jupyterlab-jbook-chapter-navigation/issues" + }, + "license": "BSD-3-Clause", + "author": { + "name": "Alex Lewandowski", + "email": "aflewandowski@alaska.edu" + }, + "files": [ + "lib/**/*.{d.ts,eot,gif,html,jpg,js,js.map,json,png,svg,woff2,ttf}", + "style/**/*.{css,js,eot,gif,html,jpg,json,png,svg,woff2,ttf}" + ], + "main": "lib/index.js", + "types": "lib/index.d.ts", + "style": "style/index.css", + "repository": { + "type": "git", + "url": "https://github.com/Alex-Lewandowski/jupyterlab-jbook-chapter-navigation.git" + }, + "scripts": { + "build": "jlpm build:lib && jlpm build:labextension:dev", + "build:prod": "jlpm clean && jlpm build:lib:prod && jlpm build:labextension", + "build:labextension": "jupyter labextension build .", + "build:labextension:dev": "jupyter labextension build --development True .", + "build:lib": "tsc --sourceMap", + "build:lib:prod": "tsc", + "clean": "jlpm clean:lib", + "clean:lib": "rimraf lib tsconfig.tsbuildinfo", + "clean:lintcache": "rimraf .eslintcache .stylelintcache", + "clean:labextension": "rimraf jlab_jbook_chapter_navigation/labextension jlab_jbook_chapter_navigation/_version.py", + "clean:all": "jlpm clean:lib && jlpm clean:labextension && jlpm clean:lintcache", + "eslint": "jlpm eslint:check --fix", + "eslint:check": "eslint . --cache --ext .ts,.tsx", + "install:extension": "jlpm build", + "lint": "jlpm stylelint && jlpm prettier && jlpm eslint", + "lint:check": "jlpm stylelint:check && jlpm prettier:check && jlpm eslint:check", + "prettier": "jlpm prettier:base --write --list-different", + "prettier:base": "prettier \"**/*{.ts,.tsx,.js,.jsx,.css,.json,.md}\"", + "prettier:check": "jlpm prettier:base --check", + "stylelint": "jlpm stylelint:check --fix", + "stylelint:check": "stylelint --cache \"style/**/*.css\"", + "test": "jest --coverage", + "watch": "run-p watch:src watch:labextension", + "watch:src": "tsc -w --sourceMap", + "watch:labextension": "jupyter labextension watch ." + }, + "dependencies": { + "@jupyterlab/application": "^4.0.0" + }, + "devDependencies": { + "@jupyterlab/builder": "^4.0.0", + "@jupyterlab/testutils": "^4.0.0", + "@types/jest": "^29.2.0", + "@types/json-schema": "^7.0.11", + "@types/react": "^18.0.26", + "@types/react-addons-linked-state-mixin": "^0.14.22", + "@typescript-eslint/eslint-plugin": "^6.1.0", + "@typescript-eslint/parser": "^6.1.0", + "css-loader": "^6.7.1", + "eslint": "^8.36.0", + "eslint-config-prettier": "^8.8.0", + "eslint-plugin-prettier": "^5.0.0", + "jest": "^29.2.0", + "npm-run-all": "^4.1.5", + "prettier": "^3.0.0", + "rimraf": "^5.0.1", + "source-map-loader": "^1.0.2", + "style-loader": "^3.3.1", + "stylelint": "^15.10.1", + "stylelint-config-recommended": "^13.0.0", + "stylelint-config-standard": "^34.0.0", + "stylelint-csstree-validator": "^3.0.0", + "stylelint-prettier": "^4.0.0", + "typescript": "~5.0.2", + "yjs": "^13.5.0" + }, + "sideEffects": [ + "style/*.css", + "style/index.js" + ], + "styleModule": "style/index.js", + "publishConfig": { + "access": "public" + }, + "jupyterlab": { + "extension": true, + "outputDir": "jlab_jbook_chapter_navigation/labextension" + }, + "eslintIgnore": [ + "node_modules", + "dist", + "coverage", + "**/*.d.ts", + "tests", + "**/__tests__", + "ui-tests" + ], + "eslintConfig": { + "extends": [ + "eslint:recommended", + "plugin:@typescript-eslint/eslint-recommended", + "plugin:@typescript-eslint/recommended", + "plugin:prettier/recommended" + ], + "parser": "@typescript-eslint/parser", + "parserOptions": { + "project": "tsconfig.json", + "sourceType": "module" + }, + "plugins": [ + "@typescript-eslint" + ], + "rules": { + "@typescript-eslint/naming-convention": [ + "error", + { + "selector": "interface", + "format": [ + "PascalCase" + ], + "custom": { + "regex": "^I[A-Z]", + "match": true + } + } + ], + "@typescript-eslint/no-unused-vars": [ + "warn", + { + "args": "none" + } + ], + "@typescript-eslint/no-explicit-any": "off", + "@typescript-eslint/no-namespace": "off", + "@typescript-eslint/no-use-before-define": "off", + "@typescript-eslint/quotes": [ + "error", + "single", + { + "avoidEscape": true, + "allowTemplateLiterals": false + } + ], + "curly": [ + "error", + "all" + ], + "eqeqeq": "error", + "prefer-arrow-callback": "error" + } + }, + "prettier": { + "singleQuote": true, + "trailingComma": "none", + "arrowParens": "avoid", + "endOfLine": "auto", + "overrides": [ + { + "files": "package.json", + "options": { + "tabWidth": 4 + } + } + ] + }, + "stylelint": { + "extends": [ + "stylelint-config-recommended", + "stylelint-config-standard", + "stylelint-prettier/recommended" + ], + "plugins": [ + "stylelint-csstree-validator" + ], + "rules": { + "csstree/validator": true, + "property-no-vendor-prefix": null, + "selector-no-vendor-prefix": null, + "value-no-vendor-prefix": null + } + } +} diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..2303d58 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,77 @@ +[build-system] +requires = ["hatchling>=1.5.0", "jupyterlab>=4.0.0,<5", "hatch-nodejs-version>=0.3.2"] +build-backend = "hatchling.build" + +[project] +name = "jlab_jbook_chapter_navigation" +readme = "README.md" +license = { file = "LICENSE" } +requires-python = ">=3.8" +classifiers = [ + "Framework :: Jupyter", + "Framework :: Jupyter :: JupyterLab", + "Framework :: Jupyter :: JupyterLab :: 4", + "Framework :: Jupyter :: JupyterLab :: Extensions", + "Framework :: Jupyter :: JupyterLab :: Extensions :: Prebuilt", + "License :: OSI Approved :: BSD License", + "Programming Language :: Python", + "Programming Language :: Python :: 3", + "Programming Language :: Python :: 3.8", + "Programming Language :: Python :: 3.9", + "Programming Language :: Python :: 3.10", + "Programming Language :: Python :: 3.11", + "Programming Language :: Python :: 3.12", +] +dependencies = [ +] +dynamic = ["version", "description", "authors", "urls", "keywords"] + +[tool.hatch.version] +source = "nodejs" + +[tool.hatch.metadata.hooks.nodejs] +fields = ["description", "authors", "urls"] + +[tool.hatch.build.targets.sdist] +artifacts = ["jlab_jbook_chapter_navigation/labextension"] +exclude = [".github", "binder"] + +[tool.hatch.build.targets.wheel.shared-data] +"jlab_jbook_chapter_navigation/labextension" = "share/jupyter/labextensions/jlab-jbook-chapter-navigation" +"install.json" = "share/jupyter/labextensions/jlab-jbook-chapter-navigation/install.json" + +[tool.hatch.build.hooks.version] +path = "jlab_jbook_chapter_navigation/_version.py" + +[tool.hatch.build.hooks.jupyter-builder] +dependencies = ["hatch-jupyter-builder>=0.5"] +build-function = "hatch_jupyter_builder.npm_builder" +ensured-targets = [ + "jlab_jbook_chapter_navigation/labextension/static/style.js", + "jlab_jbook_chapter_navigation/labextension/package.json", +] +skip-if-exists = ["jlab_jbook_chapter_navigation/labextension/static/style.js"] + +[tool.hatch.build.hooks.jupyter-builder.build-kwargs] +build_cmd = "build:prod" +npm = ["jlpm"] + +[tool.hatch.build.hooks.jupyter-builder.editable-build-kwargs] +build_cmd = "install:extension" +npm = ["jlpm"] +source_dir = "src" +build_dir = "jlab_jbook_chapter_navigation/labextension" + +[tool.jupyter-releaser.options] +version_cmd = "hatch version" + +[tool.jupyter-releaser.hooks] +before-build-npm = [ + "python -m pip install 'jupyterlab>=4.0.0,<5'", + "jlpm", + "jlpm build:prod" +] +before-build-python = ["jlpm clean:all"] + +[tool.check-wheel-contents] +ignore = ["W002"] diff --git a/setup.py b/setup.py new file mode 100644 index 0000000..aefdf20 --- /dev/null +++ b/setup.py @@ -0,0 +1 @@ +__import__("setuptools").setup() diff --git a/src/__tests__/jlab_jbook_chapter_navigation.spec.ts b/src/__tests__/jlab_jbook_chapter_navigation.spec.ts new file mode 100644 index 0000000..7d50f9e --- /dev/null +++ b/src/__tests__/jlab_jbook_chapter_navigation.spec.ts @@ -0,0 +1,9 @@ +/** + * Example of [Jest](https://jestjs.io/docs/getting-started) unit tests + */ + +describe('jlab-jbook-chapter-navigation', () => { + it('should be tested', () => { + expect(1 + 1).toEqual(2); + }); +}); diff --git a/src/index.ts b/src/index.ts new file mode 100644 index 0000000..c65acd5 --- /dev/null +++ b/src/index.ts @@ -0,0 +1,18 @@ +import { + JupyterFrontEnd, + JupyterFrontEndPlugin +} from '@jupyterlab/application'; + +/** + * Initialization data for the jlab-jbook-chapter-navigation extension. + */ +const plugin: JupyterFrontEndPlugin = { + id: 'jlab-jbook-chapter-navigation:plugin', + description: 'A JupyterLab extension that mimics jupyter-book chapter navigation on an un-built, cloned jupyter book in JupyterLab.', + autoStart: true, + activate: (app: JupyterFrontEnd) => { + console.log('JupyterLab extension jlab-jbook-chapter-navigation is activated!'); + } +}; + +export default plugin; diff --git a/style/base.css b/style/base.css new file mode 100644 index 0000000..e11f457 --- /dev/null +++ b/style/base.css @@ -0,0 +1,5 @@ +/* + See the JupyterLab Developer Guide for useful CSS Patterns: + + https://jupyterlab.readthedocs.io/en/stable/developer/css.html +*/ diff --git a/style/index.css b/style/index.css new file mode 100644 index 0000000..8a7ea29 --- /dev/null +++ b/style/index.css @@ -0,0 +1 @@ +@import url('base.css'); diff --git a/style/index.js b/style/index.js new file mode 100644 index 0000000..a028a76 --- /dev/null +++ b/style/index.js @@ -0,0 +1 @@ +import './base.css'; diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..9897917 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,23 @@ +{ + "compilerOptions": { + "allowSyntheticDefaultImports": true, + "composite": true, + "declaration": true, + "esModuleInterop": true, + "incremental": true, + "jsx": "react", + "module": "esnext", + "moduleResolution": "node", + "noEmitOnError": true, + "noImplicitAny": true, + "noUnusedLocals": true, + "preserveWatchOutput": true, + "resolveJsonModule": true, + "outDir": "lib", + "rootDir": "src", + "strict": true, + "strictNullChecks": true, + "target": "ES2018" + }, + "include": ["src/*"] +} diff --git a/tsconfig.test.json b/tsconfig.test.json new file mode 100644 index 0000000..1de37fd --- /dev/null +++ b/tsconfig.test.json @@ -0,0 +1,6 @@ +{ + "extends": "./tsconfig", + "compilerOptions": { + "types": ["jest"] + } +} diff --git a/ui-tests/README.md b/ui-tests/README.md new file mode 100644 index 0000000..8d377fc --- /dev/null +++ b/ui-tests/README.md @@ -0,0 +1,167 @@ +# Integration Testing + +This folder contains the integration tests of the extension. + +They are defined using [Playwright](https://playwright.dev/docs/intro) test runner +and [Galata](https://github.com/jupyterlab/jupyterlab/tree/main/galata) helper. + +The Playwright configuration is defined in [playwright.config.js](./playwright.config.js). + +The JupyterLab server configuration to use for the integration test is defined +in [jupyter_server_test_config.py](./jupyter_server_test_config.py). + +The default configuration will produce video for failing tests and an HTML report. + +> There is a new experimental UI mode that you may fall in love with; see [that video](https://www.youtube.com/watch?v=jF0yA-JLQW0). + +## Run the tests + +> All commands are assumed to be executed from the root directory + +To run the tests, you need to: + +1. Compile the extension: + +```sh +jlpm install +jlpm build:prod +``` + +> Check the extension is installed in JupyterLab. + +2. Install test dependencies (needed only once): + +```sh +cd ./ui-tests +jlpm install +jlpm playwright install +cd .. +``` + +3. Execute the [Playwright](https://playwright.dev/docs/intro) tests: + +```sh +cd ./ui-tests +jlpm playwright test +``` + +Test results will be shown in the terminal. In case of any test failures, the test report +will be opened in your browser at the end of the tests execution; see +[Playwright documentation](https://playwright.dev/docs/test-reporters#html-reporter) +for configuring that behavior. + +## Update the tests snapshots + +> All commands are assumed to be executed from the root directory + +If you are comparing snapshots to validate your tests, you may need to update +the reference snapshots stored in the repository. To do that, you need to: + +1. Compile the extension: + +```sh +jlpm install +jlpm build:prod +``` + +> Check the extension is installed in JupyterLab. + +2. Install test dependencies (needed only once): + +```sh +cd ./ui-tests +jlpm install +jlpm playwright install +cd .. +``` + +3. Execute the [Playwright](https://playwright.dev/docs/intro) command: + +```sh +cd ./ui-tests +jlpm playwright test -u +``` + +> Some discrepancy may occurs between the snapshots generated on your computer and +> the one generated on the CI. To ease updating the snapshots on a PR, you can +> type `please update playwright snapshots` to trigger the update by a bot on the CI. +> Once the bot has computed new snapshots, it will commit them to the PR branch. + +## Create tests + +> All commands are assumed to be executed from the root directory + +To create tests, the easiest way is to use the code generator tool of playwright: + +1. Compile the extension: + +```sh +jlpm install +jlpm build:prod +``` + +> Check the extension is installed in JupyterLab. + +2. Install test dependencies (needed only once): + +```sh +cd ./ui-tests +jlpm install +jlpm playwright install +cd .. +``` + +3. Start the server: + +```sh +cd ./ui-tests +jlpm start +``` + +4. Execute the [Playwright code generator](https://playwright.dev/docs/codegen) in **another terminal**: + +```sh +cd ./ui-tests +jlpm playwright codegen localhost:8888 +``` + +## Debug tests + +> All commands are assumed to be executed from the root directory + +To debug tests, a good way is to use the inspector tool of playwright: + +1. Compile the extension: + +```sh +jlpm install +jlpm build:prod +``` + +> Check the extension is installed in JupyterLab. + +2. Install test dependencies (needed only once): + +```sh +cd ./ui-tests +jlpm install +jlpm playwright install +cd .. +``` + +3. Execute the Playwright tests in [debug mode](https://playwright.dev/docs/debug): + +```sh +cd ./ui-tests +jlpm playwright test --debug +``` + +## Upgrade Playwright and the browsers + +To update the web browser versions, you must update the package `@playwright/test`: + +```sh +cd ./ui-tests +jlpm up "@playwright/test" +jlpm playwright install +``` diff --git a/ui-tests/jupyter_server_test_config.py b/ui-tests/jupyter_server_test_config.py new file mode 100644 index 0000000..f2a9478 --- /dev/null +++ b/ui-tests/jupyter_server_test_config.py @@ -0,0 +1,12 @@ +"""Server configuration for integration tests. + +!! Never use this configuration in production because it +opens the server to the world and provide access to JupyterLab +JavaScript objects through the global window variable. +""" +from jupyterlab.galata import configure_jupyter_server + +configure_jupyter_server(c) + +# Uncomment to set server log level to debug level +# c.ServerApp.log_level = "DEBUG" diff --git a/ui-tests/package.json b/ui-tests/package.json new file mode 100644 index 0000000..e2711ff --- /dev/null +++ b/ui-tests/package.json @@ -0,0 +1,15 @@ +{ + "name": "jlab-jbook-chapter-navigation-ui-tests", + "version": "1.0.0", + "description": "JupyterLab jlab-jbook-chapter-navigation Integration Tests", + "private": true, + "scripts": { + "start": "jupyter lab --config jupyter_server_test_config.py", + "test": "jlpm playwright test", + "test:update": "jlpm playwright test --update-snapshots" + }, + "devDependencies": { + "@jupyterlab/galata": "^5.0.5", + "@playwright/test": "^1.37.0" + } +} diff --git a/ui-tests/playwright.config.js b/ui-tests/playwright.config.js new file mode 100644 index 0000000..9ece6fa --- /dev/null +++ b/ui-tests/playwright.config.js @@ -0,0 +1,14 @@ +/** + * Configuration for Playwright using default from @jupyterlab/galata + */ +const baseConfig = require('@jupyterlab/galata/lib/playwright-config'); + +module.exports = { + ...baseConfig, + webServer: { + command: 'jlpm start', + url: 'http://localhost:8888/lab', + timeout: 120 * 1000, + reuseExistingServer: !process.env.CI + } +}; diff --git a/ui-tests/tests/jlab_jbook_chapter_navigation.spec.ts b/ui-tests/tests/jlab_jbook_chapter_navigation.spec.ts new file mode 100644 index 0000000..9f2dc7e --- /dev/null +++ b/ui-tests/tests/jlab_jbook_chapter_navigation.spec.ts @@ -0,0 +1,21 @@ +import { expect, test } from '@jupyterlab/galata'; + +/** + * Don't load JupyterLab webpage before running the tests. + * This is required to ensure we capture all log messages. + */ +test.use({ autoGoto: false }); + +test('should emit an activation console message', async ({ page }) => { + const logs: string[] = []; + + page.on('console', message => { + logs.push(message.text()); + }); + + await page.goto(); + + expect( + logs.filter(s => s === 'JupyterLab extension jlab-jbook-chapter-navigation is activated!') + ).toHaveLength(1); +}); diff --git a/ui-tests/yarn.lock b/ui-tests/yarn.lock new file mode 100644 index 0000000..e69de29