diff --git a/.github/workflows/cypress-Run-tests-with-npm-packages.yml b/.github/workflows/cypress-Run-tests-with-npm-packages.yml
deleted file mode 100644
index ae600ce55..000000000
--- a/.github/workflows/cypress-Run-tests-with-npm-packages.yml
+++ /dev/null
@@ -1,146 +0,0 @@
-# It runs all available Cypress tests and sends test data to Cypress Dashboard.
-# It executes on every Monday at 01 a.m, and on demand by the user.
-# The tests run with a fail-fast strategy, once one fails, the others will not run.
-# If a test fails, it creates a set of screenshots showing the errors.
-#
-# Matrix:
-# - Browser: chrome.
-# - Test folder: All tests available on the `/tests` folder.
-#
-# Jobs:
-# - Set up the Cypress Environment: `setup-cypress`.
-# - Runs all tests on the matrix: `run-all-tests-matrix`.
-
-name: Run Cypress tests with npm packages
-
-on:
- schedule:
- # runs every Monday at 1 a.m
- - cron: "0 1 * * 1"
- filters:
- branches:
- only:
- - stable
- workflow_dispatch:
- inputs:
- # Sends data to Cy
- cypress-record:
- description: Send test data to Cypress Dashboard? Write 'Yes' to send data.
- required: true
- default: "No"
-
-jobs:
- setup-cypress:
- # Setup Cypress environment without cache.
- name: Setup Cypress environment without cache
- runs-on: ubuntu-latest
- steps:
- # 01. Checkout the repository.
- - name: Checkout
- uses: actions/checkout@v2
-
- # 02. Install a specific version of Node using.
- - name: Use Node.js
- uses: actions/setup-node@v2
- with:
- node-version: 16
-
- # 03. Install dependencies and verify Cypress
- - name: Install dependencies and verify Cypress
- env:
- # make sure every Cypress install prints minimal information
- CI: 1
- # print Cypress and OS info
- # This next command should use "npm ci" instead of "npm install"
- run: |
- npm ci
- npx cypress verify
- npx cypress info
- npx cypress version
- npx cypress version --component package
- npx cypress version --component binary
- npx cypress version --component electron
- npx cypress version --component node
-
- run-all-tests-matrix:
- # Runs all tests.
- runs-on: ubuntu-latest
- needs: setup-cypress
- strategy:
- fail-fast: true
- matrix:
- # Define values for browsers from
- browser: ["chrome"]
- # browser: ['chrome', 'edge', 'firefox', 'chromium']
- type: ["all"]
- # type: ['smoke','e2e', 'ui', 'validation']
- # env: ['local', 'public']
- name: Run ${{ matrix.type }} tests on ${{ matrix.browser }}
- steps:
- # 01. Checkout the repository.
- - name: Checkout
- uses: actions/checkout@v2
-
- # 02. Install a specific version of Node.
- - name: Use Node.js
- uses: actions/setup-node@v2
- with:
- node-version: 16
-
- # 03a. Decide whether to send data to Cypress Dashboard, or not, for workflow_dispatch and schedule.
- - name: Decide if we send data to Cypress 2
- if: ${{ github.event.inputs.cypress-record == 'Yes' || github.event_name == 'schedule' }}
- run: |
- echo "CY_RECORD_KEY=${{ secrets.CYPRESS_RECORD_KEY }}" >> $GITHUB_ENV
- echo "CY_RECORD_FLAG=-- --record --key " >> $GITHUB_ENV
-
- # 03a. Decide whether to send data to Cypress Dashboard, or not, for workflow_dispatch.
- - name: Decide if we send data to Cypress 1
- # We don't want to send the tests to Dashboard through workflow dispatch
- if: ${{ github.event.inputs.cypress-record != 'Yes' }}
- # we set the environment variables dynamically to empty in order to avoid
- # recording the test execution to Cypress Dashboard.
- run: |
- echo "CY_RECORD_KEY=" >> $GITHUB_ENV
- echo "CY_RECORD_FLAG=" >> $GITHUB_ENV
-
- # 04. Run the tests following the initial matrix: by browser and test type
- - name: Run tests by browser
- uses: cypress-io/github-action@v2
- timeout-minutes: 10
- with:
- # 'build' starts the default demo
- build: npm run build
- # 'test:ci' runs tests over Docker image for build context
- # we also send the Cypress Dashboard record key dynamically
- command: npm run test:ci ${{ env.CY_RECORD_FLAG }} ${{ env.CY_RECORD_KEY }}
- record: true
- parallel: true
- group: "${{ matrix.type }} tests on ${{ matrix.browser }}"
- browser: ${{ matrix.browser }}
- config: "video: true"
- spec: |
- cypress/tests/**/*.js
- env:
- # https://github.com/wiris/html-integrations/settings/secrets/actions
- CYPRESS_PROJECT_ID: ${{ secrets.CYPRESS_PROJECT_ID }}
- GITHUB_TOKEN: ${{ secrets.GH_CICD_TOKEN }}
-
- # 05a. Save videos and screenshots as test artifacts
- # https://github.com/actions/upload-artifact
- - name: Upload screenshots
- uses: actions/upload-artifact@master
- # there might be no screenshots created when:
- # - there are no test failures
- # so only upload screenshots if previous step has failed
- if: failure()
- with:
- name: screenshots-${{ matrix.type }}-${{ matrix.browser }}
- path: cypress/screenshots
-
- # 05b. Upload videos, since they are always be generated.
- - name: Upload videos for all tests
- uses: actions/upload-artifact@master
- with:
- name: videos-${{ matrix.type }}-${{ matrix.browser }}
- path: cypress/videos
diff --git a/.github/workflows/run-e2e-tests.yml b/.github/workflows/run-e2e-tests.yml
new file mode 100644
index 000000000..8f4a6850e
--- /dev/null
+++ b/.github/workflows/run-e2e-tests.yml
@@ -0,0 +1,109 @@
+name: E2E Tests - All Editors
+
+on:
+ push:
+ branches:
+ - master
+ pull_request:
+ workflow_dispatch:
+
+jobs:
+ # Matrix strategy to enable per html editor parallelization
+ e2e-tests:
+ name: E2E Tests - ${{ matrix.editor }}
+ runs-on: ubuntu-latest
+ timeout-minutes: 30
+ strategy:
+ fail-fast: false
+ matrix:
+ editor:
+ - generic
+ - ckeditor4
+ - ckeditor5
+ - froala
+ - tinymce5
+ - tinymce6
+ - tinymce7
+ - tinymce8
+
+ steps:
+ - uses: actions/checkout@v5
+
+ - uses: actions/setup-node@v4
+ with:
+ node-version: "23.x"
+
+ - name: Install dependencies
+ run: |
+ yarn install
+ yarn playwright install --with-deps
+
+ - name: Build ${{ matrix.editor }} package
+ if: matrix.editor != 'tinymce8'
+ run: |
+ yarn nx build ${{ matrix.editor }}
+ yarn nx build html-${{ matrix.editor }}
+ env:
+ CKEDITOR4_API_KEY: ${{ secrets.CKEDITOR4_API_KEY }}
+ FROALA_API_KEY: ${{ secrets.FROALA_API_KEY }}
+
+ - name: Build tinymce8 packages
+ if: matrix.editor == 'tinymce8'
+ run: |
+ yarn nx build tinymce7
+ yarn nx build html-tinymce8
+
+ - name: Run E2E tests for ${{ matrix.editor }}
+ id: e2e
+ run: HTML_EDITOR=${{ matrix.editor }} PLAYWRIGHT_BLOB_OUTPUT_NAME=report-${{ matrix.editor }}.zip yarn test:e2e
+ continue-on-error: true
+ env:
+ CKEDITOR4_API_KEY: ${{ secrets.CKEDITOR4_API_KEY }}
+ FROALA_API_KEY: ${{ secrets.FROALA_API_KEY }}
+ CK5_LICENSE_KEY: ${{ secrets.CK5_LICENSE_KEY }}
+
+ - name: Publish test results for ${{ matrix.editor }}
+ uses: dorny/test-reporter@d61b558e8df85cb60d09ca3e5b09653b4477cea7 # v2.0.0
+ with:
+ name: E2E Tests - ${{ matrix.editor }}
+ path: tests/e2e/test-results/results.xml
+ reporter: java-junit
+ fail-on-error: ${{ steps.e2e.outcome == 'failure' }}
+ continue-on-error: true
+
+ - name: Upload blob report for ${{ matrix.editor }}
+ uses: actions/upload-artifact@v4
+ with:
+ name: blob-report-${{ matrix.editor }}
+ path: blob-report
+ retention-days: 1
+
+ merge-reports:
+ if: always()
+ needs: [e2e-tests]
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v5
+ - uses: actions/setup-node@v4
+ with:
+ node-version: "23.x"
+
+ - name: Install dependencies
+ run: yarn install
+
+ - name: Download all blobs
+ uses: actions/download-artifact@v4
+ with:
+ path: all-blob-reports
+ pattern: blob-report-*
+ merge-multiple: true
+
+ - name: Merge into HTML Report
+ run: npx playwright merge-reports --reporter html ./all-blob-reports
+
+ - name: Upload Final HTML Report
+ uses: actions/upload-artifact@v4
+ with:
+ name: final-playwright-report
+ path: playwright-report/
+ retention-days: 2
diff --git a/.gitignore b/.gitignore
index e2b72db8e..0576f28bc 100644
--- a/.gitignore
+++ b/.gitignore
@@ -7,10 +7,6 @@
package-lock.json
packages/*/yarn.lock
-# Cypress
-cypress/screenshots
-cypress/videos
-
node_modules
# Verdaccio
diff --git a/.vscode/extensions.json b/.vscode/extensions.json
index ce541f0a9..00c201f60 100644
--- a/.vscode/extensions.json
+++ b/.vscode/extensions.json
@@ -20,6 +20,7 @@
"ms-vsliveshare.vsliveshare", // Live Share
"visualstudioexptteam.vscodeintellicode", // IntelliCode
"thundergang.thunder-client", // Thunder Client
+ "ms-playwright.playwright", // Playwright
// GITHUB
"eamodio.gitlens", // GitLens
diff --git a/cypress.json b/cypress.json
deleted file mode 100644
index ea606fa3b..000000000
--- a/cypress.json
+++ /dev/null
@@ -1,8 +0,0 @@
-{
- "integrationFolder": "cypress/tests",
- "screenshotOnRunFailure": true,
- "env": {
- "FAIL_FAST_STRATEGY": "run",
- "FAIL_FAST_ENABLED": true
- }
-}
diff --git a/cypress/README.md b/cypress/README.md
deleted file mode 100644
index 0291ea446..000000000
--- a/cypress/README.md
+++ /dev/null
@@ -1,88 +0,0 @@
-# Cypress End-to-end tests for the MathType Web Integrations
-
-[Cypress.io](https://www.cypress.io) is an open source, MIT licensed end-to-end test runner.
-
-**Important**: More information about testing commands and how to use instructions on the [Testing section](/docs/development/testing/README.md) of this project documentation.
-
-## Folder structure
-
-These folders hold end-to-end tests and supporting files for the Cypress Test Runner.
-
-- [fixtures](fixtures) holds sample data for mocking our tests, [read more](https://on.cypress.io/fixture).
-- [tests](tests) holds the actual integration test files, [read more](https://on.cypress.io/writing-and-organizing-tests)
-- [plugins](plugins) allow you to customize how tests are loaded, [read more](https://on.cypress.io/plugins)
-- [support](support) includes our custom commands, [read more](https://on.cypress.io/writing-and-organizing-tests#Support-file)
-
-## What do we want to test?
-
-A minimal MathType Integration for the web that includes mathematical formula editing & rendering features from Wiris.
-
-### Elements
-
-It would consist on these next HTML elements:
-
-1. an editable element, with a default value or not
-2. a read-only element with its content synchronized to the previous element through an `onChange` event
-3. the MathType and ChemType buttons, over the editable content
-
-Also, whenever the MathType or ChemType buttons are clicked, or a mathematical expression inside the textarea is clicked,
-
-4. a MathType Modal Window is shown to the user to edit the formula.
-
-### Source code
-
-A canonical representation of the HTML source code of this app would look like this:
-
-```html
-
-
-
-
-
-
...
-
-
-```
-
-### UI Preview
-
-This next diagram represents a common E2E interaction with the MathType for the Web UI elements of the canonical MathType Integration sample app: adding a mathematical formula from scratch using the MathType editor.
-
-
-
-### MathType Modal Window
-
-The MathType Modal Window consists on the following elements:
-
-
-
-> This diagram is based on a comment in the source code of [`modal.js`](/packages/mathtype-html-integration-devkit/src/modal.js) from the `mathtype-html-integration-devkit` package.
-
-## `cypress.json` file
-
-You can configure project options in the [../cypress.json](../cypress.json) file, see [Cypress configuration doc](https://on.cypress.io/configuration).
-
-The current values we've set by default for all environments are:
-
-```json
- // By default, point to the demos/html/generic App.
- "baseUrl": "http://localhost:8007",
- // Override default 'integration' value to 'tests'
- "integrationFolder": "cypress/tests",
- "screenshotOnRunFailure": true,
- // Optimize test execution by activating Fail_fast feature everywhere.
- "env":
- {
- "FAIL_FAST_STRATEGY": "run",
- "FAIL_FAST_ENABLED": true
- }
-
-```
-
-The main cypress.json files will hold the default settings for all tests in all environments: local, build, ...
-
-## More information
-
-- [https://github.com/cypress.io/cypress](https://github.com/cypress.io/cypress)
-- [https://docs.cypress.io/](https://docs.cypress.io/)
-- [Writing your first Cypress test](http://on.cypress.io/intro)
diff --git a/cypress/YYY-ZZZ.category.js b/cypress/YYY-ZZZ.category.js
deleted file mode 100644
index aca165ed6..000000000
--- a/cypress/YYY-ZZZ.category.js
+++ /dev/null
@@ -1,32 +0,0 @@
-///
-// ***********************************************************
-// Test case: {Test.ID} Ex. INT-STD-014
-// Title: {Test.Title} Ex. User creates a new formula from scratch using MathType.
-// Document: {Test.URL} Ex. https://docs.google.com/document/d/1fiGsUwqNIsjiaJI0aGfH_aNX5OJKEHkfWtfvlQkEEFI/edit
-// Context: {Test.Type} - {Text.category} Ex. UI - Formula insertion/edition
-// Issue: {Test.Issue} Ex. KB-99999
-// ***********************************************************
-
-describe("Formula insertion/edition", () => {
- beforeEach(() => {
- // Load fixture data
- cy.fixture("formulas.json").as("formulas");
- // and visit page.
- cy.visit("/");
- // Eventually, clear the editor content: by default the editor could include a mathematical expression.
- cy.getTextEditor().clear();
- });
-
- it("User creates a new formula from scratch using MathType", function () {
- // Open a new MathType modal window clicking the button
- cy.clickButtonToOpenModal();
- // then type a general formula inside the editor
- cy.typeInModal(this.formulas["formula-general"]);
- // and insert the formula at the beginning of the target element using the 'Insert' button.
- cy.clickModalButton("insert");
-
- // Check the recently inserted formula
- // and validate is rendered succesfully using MathType services.
- cy.getFormula(0).isRendered();
- });
-});
diff --git a/cypress/fixtures/formulas.json b/cypress/fixtures/formulas.json
deleted file mode 100644
index 8623cf382..000000000
--- a/cypress/fixtures/formulas.json
+++ /dev/null
@@ -1,20 +0,0 @@
-{
- "formula-general": "x + y",
- "formula-general-alt-en": "x space plus space y",
- "formula-general-alt-es": "x espacio más espacio y",
- "formula-addition": " = z",
- "formula-total-alt-es": "x espacio más espacio y espacio igual espacio z",
- "text-alignment": "2222",
- "formula-alignment": "2+2",
- "latex-general": "$$\\cos^2(x)+\\sin^2(x)$$",
- "latex-general-alt-en": "cos squared left parenthesis x right parenthesis plus sin squared left parenthesis x right parenthesis",
- "latex-addition": "=log(e)",
- "quadratic": "",
- "quadratic-accessible-en": "x equals fraction numerator negative b plus-or-minus square root of b squared minus 4 a c end root over denominator 2 a end fraction",
- "formula-drawn": [
- { "x": 0, "y": 0 },
- { "x": 0, "y": 1 },
- { "x": 1, "y": 0 },
- { "x": 1, "y": 1 }
- ]
-}
diff --git a/cypress/mathtype-web-app.png b/cypress/mathtype-web-app.png
deleted file mode 100644
index b2c4cb2d2..000000000
Binary files a/cypress/mathtype-web-app.png and /dev/null differ
diff --git a/cypress/modal.jpg b/cypress/modal.jpg
deleted file mode 100644
index f49ecee86..000000000
Binary files a/cypress/modal.jpg and /dev/null differ
diff --git a/cypress/plugins/index.js b/cypress/plugins/index.js
deleted file mode 100644
index 4c63d2db1..000000000
--- a/cypress/plugins/index.js
+++ /dev/null
@@ -1,24 +0,0 @@
-///
-// ***********************************************************
-// This example plugins/index.js can be used to load plugins
-//
-// You can change the location of this file or turn off loading
-// the plugins file with the 'pluginsFile' configuration option.
-//
-// You can read more here:
-// https://on.cypress.io/plugins-guide
-// ***********************************************************
-
-// This function is called when a project is opened or re-opened (e.g. due to
-// the project's config changing)
-
-/**
- * @type {Cypress.PluginConfig}
- */
-module.exports = (on, config) => {
- // `on` is used to hook into various events Cypress emits
- // `config` is the resolved Cypress config
- // eslint-disable-next-line global-require
- require("cypress-fail-fast/plugin")(on, config);
- return config;
-};
diff --git a/cypress/support/commands.d.ts b/cypress/support/commands.d.ts
deleted file mode 100644
index 7a95b4714..000000000
--- a/cypress/support/commands.d.ts
+++ /dev/null
@@ -1,119 +0,0 @@
-///
-
-declare namespace Cypress {
- interface Chainable {
- /**
- * Yield the text editor.
- */
- getTextEditor();
-
- /**
- * Click the MathType or ChemType button in the text editor.
- * @param chem {default = false} whether to click the MathType or ChemType button.
- */
- clickButtonToOpenModal(chem?: boolean);
-
- /**
- * Append the given string in the text editor at the given offset.
- * @param text string to append in the editor
- * @param offset position of the caret when typing. At the end if omitted. Not yet implemented
- */
- typeInTextEditor(text: string, offset?: number);
-
- /**
- * Append the given text in MathType.
- * @param formula formula to append in the editor
- * @param paste whether the text appended is typed or pasted. Not yet implemented
- */
- typeInModal(formula: string, paste?: boolean);
-
- /**
- * Click the specified button on the MathType modal dialog
- * @param {string} button Button identifier. Values can be:
- * - insert: inserts a formula
- * - cancel: closes the modal. If there are changes, opens a confirmation dialog
- * - confirmationClose: discards the changes when closing the modal dialog
- * - confirmationCancel: cancels the confirmation dialog and returns to editing the formula
- * - xClose: closes the modal through the top right x button
- * - maximize: makes the modal full screen through the top right button
- * - stack: changes the modal to not be full screen through the top right button
- * - minimize: hides the modal, if visible, and shows it again, if not visible, through the top right button
- * - hand: opens/closes Hand mode
- */
- clickModalButton(button: string);
-
- /**
- * Insert a formula from scratch
- * @param formula formula to append in the editor
- * @param chem {default = false} whether to click the MathType or ChemType button
- * @param paste {default = false} whether the text appended is typed or pasted. Not yet implemented
- */
- insertFormulaFromScratch(formula: string, chem?: boolean, paste?: boolean);
-
- /**
- * Obtain a formula from a given identifier.
- * @param formulaId identifier of the formula to obtain. The identifier is the 0-indexed position of the formula inside the text editor.
- * @returns the formula
- */
- getFormula(formulaId: number): Chainable;
-
- /**
- * Select a LaTeX formula from a given identifier.
- * @param formulaId id of the formula to obtain. The id is the 0-indexed position of the formula inside the text editor.
- * @returns the formula
- */
- selectLatexFormula(formulaId: number): Chainable;
-
- /**
- * Edit an existing MathType formula by clicking the MathType or ChemType button.
- * Must be applied to a father command unless latex is set to true.
- * @param subject the formula to apply this command to. Not yet implemented
- * @param options object with options:
- * chem {default = false} whether to edit a chem or math formula
- * latex {default = false} whether it is a LaTeX formula or not
- * formulaId id of the formula to edit. Only used when latex is set to true
- * formula string to be added when editing the formula
- */
- editFormula(
- subject: Element,
- formula: string,
- options?: { chem?: boolean; latex?: boolean; formulaId?: number },
- ): Chainable | null;
-
- /**
- * Press the ESC keyboard button
- */
- pressESCButton();
-
- /**
- * Not yet implemented.
- * Drag and drop the MathType or ChemType modal
- * @param coordinates place to drop the modal
- */
- dragDropModal(coordinates: { x: number; y: number });
-
- /**
- * Not yet implemented.
- * Drag and drop a Formula.
- * Must be applied to a father command.
- * @param subject the formula to apply this command to
- * @param coordinates place to drop the formula
- */
- dragDropFormula(subject: Element, coordinates: { x: number; y: number }): Chainable;
-
- /**
- * Not yet implemented.
- * Draw a formula with Hand mode.
- * @param points list of ordered coordinates to draw
- */
- drawFormula(points: { x: number; y: number }[]);
-
- /**
- * Not yet implemented.
- * Resize a given formula.
- * Must be applied to a father command.
- * @param subject the formula to apply this command to
- */
- resizeFormula(subject: Element): Chainable;
- }
-}
diff --git a/cypress/support/commands.js b/cypress/support/commands.js
deleted file mode 100644
index 66cfd3a8b..000000000
--- a/cypress/support/commands.js
+++ /dev/null
@@ -1,161 +0,0 @@
-import { createSelection } from "./utils";
-
-Cypress.Commands.add("getTextEditor", () => {
- cy.get("div[contenteditable]");
-});
-
-Cypress.Commands.add("clickButtonToOpenModal", (chem = false) => {
- if (!chem) {
- cy.get("#editorIcon").click();
- } else {
- cy.get("#chemistryIcon").click();
- }
- cy
- // We wait for the toolbar to load before typing, as it is a good indicator of whether MathType has fully loaded.
- // This depends on the implementation of the modal and is not too desirable, but works well.
- .get(".wrs_toolbar")
- .should("be.visible");
-});
-
-Cypress.Commands.add("typeInTextEditor", (text) => {
- cy.getTextEditor().type(text);
-});
-
-Cypress.Commands.add("typeInModal", (formula) => {
- cy.get(".wrs_focusElementContainer > input").type(formula);
-});
-
-Cypress.Commands.add("clickModalButton", (button) => {
- switch (button) {
- case "insert":
- cy.get(".wrs_modal_button_accept").click();
- break;
- case "cancel":
- cy.get(".wrs_modal_button_cancel").click();
- break;
- case "confirmationClose":
- cy.get("#wrs_popup_accept_button").click({ force: true });
- break;
- case "confirmationCancel":
- cy.get("#wrs_popup_cancel_button").click({ force: true });
- break;
- case "xClose":
- cy.get(".wrs_modal_close_button").click();
- break;
- case "maximize":
- cy.get(".wrs_modal_maximize_button").click();
- break;
- case "stack":
- cy.get(".wrs_modal_stack_button").click();
- break;
- case "minimize":
- cy.get(".wrs_modal_minimize_button").click();
- break;
- case "hand":
- cy.get(".wrs_handWrapper > input").click();
- break;
- default:
- throw new Error(`The button '${button}' does not exist. Check the clickModalButton documentation.`);
- }
-});
-
-// eslint-disable-next-line no-unused-vars
-Cypress.Commands.add("insertFormulaFromScratch", (formula, chem = false, paste = false) => {
- // Open the mathtype modal
- cy.clickButtonToOpenModal(chem);
-
- // Type the formula that matxes the previous inserted text on the mathtype modal
- cy.typeInModal(formula);
-
- // Insert the formula
- cy.clickModalButton("insert");
-});
-
-Cypress.Commands.add("getFormula", (formulaId) => {
- cy.get(".Wirisformula")
- .should("have.length.at.least", formulaId + 1)
- .eq(formulaId);
-});
-
-Cypress.Commands.add("selectLatexFormula", (formulaId) => {
- let block;
- let startOffset;
- let endOffset;
-
- // Get the block, and offsets of the latex formula
- let countFormula = 0;
- const edit = Cypress.$("#editable");
- const kids = edit[0].children;
- for (let j = 0; j < kids.length; ++j) {
- const elem = kids[j];
- const html = elem.innerHTML;
- let prevDolar = false;
- let waitEndDolar = false;
- for (let i = 0; i < html.length; i++) {
- const caracter = html[i];
- if (caracter === "$" && prevDolar === false && waitEndDolar === false) {
- prevDolar = true;
- } else if (caracter === "$" && prevDolar === true && waitEndDolar === false) {
- prevDolar = false;
- waitEndDolar = true;
- if (countFormula === formulaId) startOffset = i + 1;
- } else if (caracter === "$" && prevDolar === false && waitEndDolar === true) {
- prevDolar = true;
- if (countFormula === formulaId) endOffset = i;
- else countFormula += 1;
- } else if (caracter === "$" && prevDolar === true && waitEndDolar === true) {
- prevDolar = false;
- waitEndDolar = false;
- }
- if (startOffset && endOffset) {
- block = j;
- break;
- }
- }
- if (startOffset && endOffset) break;
- }
-
- // Throw error if the latex formula identifier does not correspond to a latex formula on the test
- if (!startOffset || !endOffset) {
- throw new Error(`The latex formula number '${formulaId}' does not exist`);
- }
-
- // Select the latex formula
- cy.getTextEditor()
- .children()
- .eq(block)
- .trigger("mousedown")
- .then(($el) => {
- createSelection($el, startOffset, endOffset);
- })
- .trigger("mouseup");
- cy.document().trigger("selectionchange");
-});
-
-Cypress.Commands.add(
- "editFormula",
- { prevSubject: "optional" },
- (
- subject,
- formula,
- options = {
- chem: false,
- latex: false,
- formulaId: 0,
- },
- ) => {
- // Select the latex formula and edit it
- if (options.latex) {
- cy.selectLatexFormula(options.formulaId);
- cy.clickButtonToOpenModal(options.chem);
- cy.typeInModal(formula);
- cy.clickModalButton("insert");
- } else {
- throw new Error("Not implemented yet");
- }
- },
-);
-
-Cypress.Commands.add("pressESCButton", () => {
- cy.get("body").type("{esc}");
-});
diff --git a/cypress/support/index.js b/cypress/support/index.js
deleted file mode 100644
index fececccb1..000000000
--- a/cypress/support/index.js
+++ /dev/null
@@ -1,22 +0,0 @@
-// ***********************************************************
-// This example support/index.js is processed and
-// loaded automatically before your test files.
-//
-// This is a great place to put global configuration and
-// behavior that modifies Cypress.
-//
-// You can change the location of this file or turn off
-// automatically serving support files with the
-// 'supportFile' configuration option.
-//
-// You can read more here:
-// https://on.cypress.io/configuration
-// ***********************************************************
-
-// Import commands.js using ES2015 syntax:
-import "./commands";
-import "./validations";
-import "cypress-fail-fast";
-
-// Alternatively you can use CommonJS syntax:
-// require('./commands')
diff --git a/cypress/support/utils.js b/cypress/support/utils.js
deleted file mode 100644
index a2d60c7c6..000000000
--- a/cypress/support/utils.js
+++ /dev/null
@@ -1,19 +0,0 @@
-/**
- * Create a selection in the document in the given field, at the given start and end positions.
- * @param field DOM element to make the selection in
- * @param start index of the character to start the selecion in
- * @param end index of the character to end the selection in
- */
-function createSelection(field, start, end) {
- const el = field[0];
- const document = el.ownerDocument;
- const range = document.createRange();
- range.selectNodeContents(el);
- document.getSelection().removeAllRanges(range);
- if (start) range.setStart(el.firstChild, start);
- if (end) range.setEnd(el.firstChild, end);
- document.getSelection().addRange(range);
-}
-
-// eslint-disable-next-line import/prefer-default-export
-export { createSelection };
diff --git a/cypress/support/validations.d.ts b/cypress/support/validations.d.ts
deleted file mode 100644
index 83fc62762..000000000
--- a/cypress/support/validations.d.ts
+++ /dev/null
@@ -1,37 +0,0 @@
-///
-
-declare namespace Cypress {
- interface Chainable {
- /**
- * Validate that the height of the given formula and the surrounding text is the same.
- * Must be applied to a father command.
- * @param subject the formula to apply this validation to
- * @param preview {default = false} whether to check aligment on the preview or on the text editor
- */
- isAligned(subject: Element, preview?: boolean): Chainable;
-
- /**
- * Check that the given formula is rendered in the preview mode or the text editor area.
- * Must be applied to a father command.
- * @param subject the formula to apply this validation to
- * @param preview {default = false} whether to check aligment on the preview or on the text editor
- */
- isRendered(subject: Element, preview?: boolean): Chainable;
-
- /**
- * Validates that Hand mode is activated.
- */
- isHandModeOn();
-
- /**
- * Validates that the ChemType modal is open.
- */
- isChemTypeOn();
-
- /**
- * Check that the text inside the modal matches the given text string.
- * @param text the text to match the modal content against
- */
- modalTextEquals(text: string);
- }
-}
diff --git a/cypress/support/validations.js b/cypress/support/validations.js
deleted file mode 100644
index dc5f2cebd..000000000
--- a/cypress/support/validations.js
+++ /dev/null
@@ -1,4 +0,0 @@
-// eslint-disable-next-line no-unused-vars
-Cypress.Commands.add("isRendered", { prevSubject: "element" }, (subject, preview = false) => {
- cy.wrap(subject).should("be.visible");
-});
diff --git a/cypress/tests/e2e/STD-018.insertion.js b/cypress/tests/e2e/STD-018.insertion.js
deleted file mode 100644
index cebb8a13a..000000000
--- a/cypress/tests/e2e/STD-018.insertion.js
+++ /dev/null
@@ -1,29 +0,0 @@
-///
-// ***********************************************************
-// Test case: INT-STD-018
-// Title: User writes a latex formula and visualizes it on preview.
-// Document: https://docs.google.com/document/d/1fiGsUwqNIsjiaJI0aGfH_aNX5OJKEHkfWtfvlQkEEFI/edit
-// Context: E2E / Insertion
-// Issue: KB-13069
-// ***********************************************************
-beforeEach(() => {
- // Load fixture data
- cy.fixture("formulas.json").as("formulas");
-
- // Visit the page.
- cy.visit("/");
-
- // Clear the editor content in order to reduce noise
- cy.getTextEditor().clear();
-});
-
-it("an inserted latex formula should be rendered on preview", function () {
- // Type the formula that matxes the previous inserted text on the mathtype modal
- cy.typeInTextEditor(this.formulas["latex-general"]);
-
- // // Click the update button
- // cy.get('#btn_update').click();
-
- // // Assert that the vertical align is -4px, which means that is aligner vertically (base) to the previous writen 2222
- // cy.getFormula(0).isRendered().and('have.attr', 'alt', this.formulas['latex-general-alt-en']);
-});
diff --git a/cypress/tests/e2e/STD-026.modal.js b/cypress/tests/e2e/STD-026.modal.js
deleted file mode 100644
index 94a61f222..000000000
--- a/cypress/tests/e2e/STD-026.modal.js
+++ /dev/null
@@ -1,38 +0,0 @@
-///
-// ***********************************************************
-// Test case: INT-STD-026
-// Title: Validate Hand formulas open Hand directly when edited
-// Document: https://docs.google.com/document/d/10nBVV0y3O5Eo7hEHtok8-s8zsZqVId7s_jwT5vziy7g/edit
-// Context: E2E / Modal
-// Issue: -
-// ***********************************************************
-beforeEach(() => {
- // Load fixtures
- cy.fixture("formulas.json").as("formulas");
-
- // Visit page
- cy.visit("/");
-
- // Clear the editor content in order to reduce noise
- cy.getTextEditor().clear();
-});
-
-it("Validate Hand formulas open Hand directly when edited", function () {
- // Click the MT button on the HTML editor toolbar
- cy.clickButtonToOpenModal();
-
- // Switch to Hand editing mode by clicking the Hand icon in the MT modal window
- // Draw a formula
- // Instead of drawing the formula by hand, we type them in and let Hand transform them
- cy.typeInModal(this.formulas["formula-general"]);
- cy.clickModalButton("hand");
-
- // Click the OK button in the MT modal window
- cy.clickModalButton("insert");
-
- // Double-click the created Hand formula
- cy.getFormula(0).dblclick();
-
- // MT modal window opens and Hand editing mode is already displayed with the formula
- cy.get("canvas").should("be.visible");
-});
diff --git a/cypress/tests/e2e/STD-028.modal.js b/cypress/tests/e2e/STD-028.modal.js
deleted file mode 100644
index 7bf395bf2..000000000
--- a/cypress/tests/e2e/STD-028.modal.js
+++ /dev/null
@@ -1,32 +0,0 @@
-///
-// ***********************************************************
-// Test case: INT-STD-028
-// Title: Validate switch between CT and MT, and viceversa
-// Document: https://docs.google.com/document/d/1PlqZUsfta5GMXXRq89oy50NTW5zBQCeUi8YBdO83Yug/edit
-// Context: E2E / Modal
-// Issue: -
-// ***********************************************************
-beforeEach(() => {
- // Load fixtures
- cy.fixture("formulas.json").as("formulas");
-
- // Visit page
- cy.visit("/");
-});
-
-it("Validate switch between CT and MT, and viceversa", () => {
- // Click the MT icon in the HTML editor toolbar.
- cy.clickButtonToOpenModal();
-
- // Click the CT icon in the HTML editor toolbar
- cy.clickButtonToOpenModal(true);
-
- // MT modal window changes to CT modal window.
- cy.get(".wrs_modal_title").eq(0).should("have.text", "ChemType");
-
- // Click the MT icon in the HTML editor toolbar.
- cy.clickButtonToOpenModal();
-
- // CT modal window changes to MT modal window.
- cy.get(".wrs_modal_title").eq(0).should("have.text", "MathType");
-});
diff --git a/cypress/tests/sandbox/.gitkeep b/cypress/tests/sandbox/.gitkeep
deleted file mode 100644
index e69de29bb..000000000
diff --git a/cypress/tests/smoke/.gitkeep b/cypress/tests/smoke/.gitkeep
deleted file mode 100644
index e69de29bb..000000000
diff --git a/cypress/tests/smoke/STD-001.insertion.js b/cypress/tests/smoke/STD-001.insertion.js
deleted file mode 100644
index f8ef0540b..000000000
--- a/cypress/tests/smoke/STD-001.insertion.js
+++ /dev/null
@@ -1,29 +0,0 @@
-///
-// ***********************************************************
-// Test case: INT-STD-001
-// Title: Validate alignment of a formula after insertion.
-// Document: https://docs.google.com/document/d/1RTZlelOssfwWAqx-ilTvRatrEQoaFIpk6ErWa7xMwIw/edit
-// Context: UI / Insertion
-// Issue: KB-13069
-// ***********************************************************
-beforeEach(() => {
- // Load fixture data
- cy.fixture("formulas.json").as("formulas");
-
- // Visit the page.
- cy.visit("/");
-
- // Clear the editor content in order to reduce noise
- cy.getTextEditor().clear();
-});
-
-it("an inserted formula that looks like plain text should be aligned with the same plain text", function () {
- // Type the text plane on the text editor
- cy.typeInTextEditor(this.formulas["text-alignment"]);
-
- // Insert a new MathType formula from scratch on the editor
- cy.insertFormulaFromScratch(this.formulas["formula-alignment"]);
-
- // Assert that the vertical align is -4px, which means that is aligner vertically (base) to the previous writen 2222
- cy.getFormula(0).should("have.attr", "style").and("contain", "vertical-align: -4px");
-});
diff --git a/cypress/tests/smoke/STD-002.insertion.js b/cypress/tests/smoke/STD-002.insertion.js
deleted file mode 100644
index 42b4f6590..000000000
--- a/cypress/tests/smoke/STD-002.insertion.js
+++ /dev/null
@@ -1,32 +0,0 @@
-///
-// ***********************************************************
-// Test case: INT-STD-002
-// Title: Validate alignment of a formula on preview.
-// Document: https://docs.google.com/document/d/1aAPzvAe8WEEXgZECLsmml07TG4l3Fdy3AlJiaFSp6Iw/edit
-// Context: UI / Insertion
-// Issue: KB-13069
-// ***********************************************************
-beforeEach(() => {
- // Load fixture data
- cy.fixture("formulas.json").as("formulas");
-
- // Visit the page.
- cy.visit("/");
-
- // Clear the editor content in order to reduce noise
- cy.getTextEditor().clear();
-});
-
-it("an inserted formula that looks like plain text should be aligned with the same plane text on preview", function () {
- // Type the text plane on the text editor
- cy.typeInTextEditor(this.formulas["text-alignment"]);
-
- // Insert a new MathType formula from scratch on the editor
- cy.insertFormulaFromScratch(this.formulas["formula-alignment"]);
-
- // // Click the update button
- // cy.get('#btn_update').click();
-
- // // Assert that the vertical align is -4px, which means that is aligner vertically (base) to the previous writen 2222
- // cy.getFormula(1).should('have.attr', 'style').and('contain', 'vertical-align: -4px');
-});
diff --git a/cypress/tests/smoke/STD-003.insertion.js b/cypress/tests/smoke/STD-003.insertion.js
deleted file mode 100644
index d6818bc2a..000000000
--- a/cypress/tests/smoke/STD-003.insertion.js
+++ /dev/null
@@ -1,36 +0,0 @@
-///
-// ***********************************************************
-// Test case: INT-STD-003
-// Title: Validate caret is placed after the inserted formula
-// Document: https://docs.google.com/document/d/1YjSGL5yfvdMQOrFrqL48tQ2vUgfxD6YNSUgqsH65xI8/edit
-// Context: UI / Insertion
-// Issue: -
-// ***********************************************************
-beforeEach(() => {
- // Load fixture data
- cy.fixture("formulas.json").as("formulas");
-
- // Visit the page.
- cy.visit("/");
-
- // Clear the editor content in order to reduce noise
- cy.getTextEditor().clear();
-});
-
-it("Validate Hand formulas open Hand directly when edited", function () {
- // Insert a new MathType formula from scratch on the editor
- cy.insertFormulaFromScratch(this.formulas["formula-general"]);
-
- // User types the string ‘wiris’ on the HTML editor
- cy.typeInTextEditor("wiris");
-
- // The string wiris is written right after the formula
- cy.getTextEditor()
- .children()
- .first() // First paragraph
- .then(($p) => {
- // Get the second node inside the paragraph
- cy.wrap($p[0].childNodes[1].textContent);
- })
- .should("eq", "wiris");
-});
diff --git a/cypress/tests/smoke/STD-004.images.js b/cypress/tests/smoke/STD-004.images.js
deleted file mode 100644
index 648bdb82e..000000000
--- a/cypress/tests/smoke/STD-004.images.js
+++ /dev/null
@@ -1,28 +0,0 @@
-///
-// ***********************************************************
-// Test case: INT-STD-004
-// Title: Validate wiris formulas contain class with value Wirisformula.
-// Document: https://docs.google.com/document/d/1LCM0z-kmZKdwpSMnrosMsyRVmZ5Zg5TNw_-VEkYyZng/edit
-// Context: Integration / Image
-// Issue: KB-13069
-// ***********************************************************
-beforeEach(() => {
- // Load fixture data
- cy.fixture("formulas.json").as("formulas");
-
- // Visit the page.
- cy.visit("/");
-
- // Clear the editor content in order to reduce noise
- cy.getTextEditor().clear();
-});
-
-it("formula should have wirisformula class", function () {
- // Insert a new MathType formula from scratch on the editor
- cy.insertFormulaFromScratch(this.formulas["formula-general"]);
-
- // Get the formula by it's alt text and assert it has the Wirisformula class
- // We could find the formula by using getFormula, but internally, that looks for
- // .Wirisformula, so it defeats the purpose. That's why we use the alt instead.
- cy.get(`img[alt="${this.formulas["formula-general-alt-es"]}"]`).should("have.class", "Wirisformula");
-});
diff --git a/cypress/tests/smoke/STD-005.images.js b/cypress/tests/smoke/STD-005.images.js
deleted file mode 100644
index d0673d1d4..000000000
--- a/cypress/tests/smoke/STD-005.images.js
+++ /dev/null
@@ -1,35 +0,0 @@
-///
-// ***********************************************************
-// Test case: INT-STD-005
-// Title: Validate formula height and width is correct.
-// Document: https://docs.google.com/document/d/167zTPA2JxtbPaxdCp8kBKIEHoRyjHZnIWYmOPLEEMY4/edit
-// Context: UI / Image
-// Issue: KB-13069
-// ***********************************************************
-beforeEach(() => {
- // Visit the page.
- cy.visit("/");
-
- // Clear the editor content in order to reduce noise
- cy.getTextEditor().clear();
-});
-
-it("The inserted formula should have the correct width and height", () => {
- // Open the mathtype modal
- cy.clickButtonToOpenModal();
-
- // Write a mathtype formula: x/3
- cy.typeInModal("{ctrl}/").type("x").type("{downarrow}3");
-
- // Insert the written formula by clicking the insert button on the modal
- cy.clickModalButton("insert");
-
- // Get the previous inserted formula
- cy.getFormula(0).then(($formula) => {
- const formula = $formula[0];
-
- // Assert that the width and height are the ones writen in the test case for the inserted formula
- expect(formula.width).to.equal(18);
- expect(formula.height).to.equal(41);
- });
-});
diff --git a/cypress/tests/smoke/STD-007.images.js b/cypress/tests/smoke/STD-007.images.js
deleted file mode 100644
index d743c7340..000000000
--- a/cypress/tests/smoke/STD-007.images.js
+++ /dev/null
@@ -1,26 +0,0 @@
-///
-// ***********************************************************
-// Test case: INT-STD-007
-// Title: Validate wiris formulas contain alt attribute.
-// Document: https://docs.google.com/document/d/1Sa83zG7-sRpS1WIPQNTLtaeUFrwqZVSpdTbrtnkZHVI/edit
-// Context: UI / Image
-// Issue: KB-13069
-// ***********************************************************
-beforeEach(() => {
- // Load fixture data
- cy.fixture("formulas.json").as("formulas");
-
- // Visit the page.
- cy.visit("/");
-
- // Clear the editor content in order to reduce noise
- cy.getTextEditor().clear();
-});
-
-it("A wiris formula should have the alt attribute", function () {
- // Insert a new MathType formula from scratch on the editor
- cy.insertFormulaFromScratch(this.formulas["formula-general"]);
-
- // Get the previous inserted formula
- cy.getFormula(0).should("have.attr", "alt");
-});
diff --git a/cypress/tests/smoke/STD-010.images.js b/cypress/tests/smoke/STD-010.images.js
deleted file mode 100644
index 12ee07397..000000000
--- a/cypress/tests/smoke/STD-010.images.js
+++ /dev/null
@@ -1,30 +0,0 @@
-///
-// ***********************************************************
-// Test case: INT-STD-010
-// Title: Validate the formula is the same on editing mode and on preview .
-// Document: https://docs.google.com/document/d/1bRxBBG_OLS_1HTGdOBRbHHIRJPZHboVJ2Zy68z80fyY/edit
-// Context: UI / Images
-// Issue: KB-13069
-// ***********************************************************
-beforeEach(() => {
- // Load fixture data
- cy.fixture("formulas.json").as("formulas");
-
- // Visit the page.
- cy.visit("/");
-
- // Clear the editor content in order to reduce noise
- cy.getTextEditor().clear();
-});
-
-it("an inserted formula should be the same on preview when this is updated", function () {
- // Insert a new MathType formula from scratch on the editor
- cy.insertFormulaFromScratch(this.formulas["formula-general"], true);
-
- // // Click the update button
- // cy.get('#btn_update').click();
-
- // // Assert that the vertical align is -4px, which means that is aligner vertically (base) to the previous writen 2222
- // cy.getFormula(0).isRendered().and('have.attr', 'alt', this.formulas['formula-general-alt-es']);
- // cy.getFormula(1).isRendered().and('have.attr', 'alt', this.formulas['formula-general-alt-en']);
-});
diff --git a/cypress/tests/smoke/STD-011.modal.js b/cypress/tests/smoke/STD-011.modal.js
deleted file mode 100644
index 0f11b4449..000000000
--- a/cypress/tests/smoke/STD-011.modal.js
+++ /dev/null
@@ -1,20 +0,0 @@
-///
-// ***********************************************************
-// Test case: INT-STD-011
-// Title: Check Hand icon is visible.
-// Document: https://docs.google.com/document/d/12cxOZRwLVhE_Aby2Ckjjee2WWJcTuceOcXmuBgZBAlE/edit
-// Context: UI / Modal
-// Issue: KB-13069
-// ***********************************************************
-beforeEach(() => {
- // Visit the page.
- cy.visit("/");
-});
-
-it("Hand icon should be visible on the mathtype modal", () => {
- // Open the mathtype modal
- cy.clickButtonToOpenModal();
-
- // Check that the hand button is visible on mathtype modal
- cy.get(".wrs_handWrapper > input").should("be.visible");
-});
diff --git a/cypress/tests/smoke/STD-012.modal.js b/cypress/tests/smoke/STD-012.modal.js
deleted file mode 100644
index e8c73abc2..000000000
--- a/cypress/tests/smoke/STD-012.modal.js
+++ /dev/null
@@ -1,32 +0,0 @@
-///
-// ***********************************************************
-// Test case: INT-STD-011
-// Title: Check Minimize, Maximize, and Close icons are visible in the modal.
-// Document: https://docs.google.com/document/d/1soW156YvORb3TIumKmlIjhpRccme5c_YyQhVLypIh0s/edit
-// Context: UI / Modal
-// Issue: KB-13069
-// ***********************************************************
-describe("Resize modal icons are visible", () => {
- beforeEach(() => {
- // Visit the page.
- cy.visit("/");
-
- // Open the mathtype modal
- cy.clickButtonToOpenModal();
- });
-
- it("minimize icon should be visible on mathtype modal", () => {
- // Check that minimize button is visible on mathtype modal
- cy.get(".wrs_modal_minimize_button").should("be.visible");
- });
-
- it("maximize icon should be visible on mathtype modal", () => {
- // Check that minimize button is visible on mathtype modal
- cy.get(".wrs_modal_maximize_button").should("be.visible");
- });
-
- it("close icon should be visible on mathtype modal", () => {
- // Check that minimize button is visible on mathtype modal
- cy.get(".wrs_modal_close_button").should("be.visible");
- });
-});
diff --git a/cypress/tests/smoke/STD-014.insertion.js b/cypress/tests/smoke/STD-014.insertion.js
deleted file mode 100644
index 752ff3d32..000000000
--- a/cypress/tests/smoke/STD-014.insertion.js
+++ /dev/null
@@ -1,23 +0,0 @@
-///
-
-beforeEach(() => {
- // Load fixtures
- cy.fixture("formulas.json").as("formulas");
-
- // Visit page
- cy.visit("/");
-
- // Clear the editor content in order to reduce noise
- cy.getTextEditor().clear();
-});
-
-it("User creates a new formula from scratch using MT", function () {
- // Insert a new MathType formula from scratch on the editor
- cy.insertFormulaFromScratch(this.formulas["formula-general"]);
-
- // MT editor modal window is closed.
- cy.get(".wrs_modal_dialogContainer").should("not.to.be.visible");
-
- // The formula is inserted at the beginning of the HTML editor content and perfectly rendered
- cy.getFormula(0).isRendered();
-});
diff --git a/cypress/tests/smoke/STD-016.insertion.js b/cypress/tests/smoke/STD-016.insertion.js
deleted file mode 100644
index 807c9d38d..000000000
--- a/cypress/tests/smoke/STD-016.insertion.js
+++ /dev/null
@@ -1,46 +0,0 @@
-///
-// ***********************************************************
-// Test case: INT-STD-016
-// Title: User edits a formula by Double-click and inserts it.
-// Document: https://docs.google.com/document/d/1bIZOmDigkvhMCpAcTf81nz3Wp252aZpyPol9AxY0OXY/edit
-// Context: E2E / Insertion
-// Issue: KB-13069
-// ***********************************************************
-beforeEach(() => {
- // Load fixture data
- cy.fixture("formulas.json").as("formulas");
-
- // Visit the page.
- cy.visit("/");
-
- // Clear the editor content in order to reduce noise
- cy.getTextEditor().clear();
-});
-
-it("should be able to edit an existing formula", { retries: 3 }, function () {
- // Insert a new MathType formula from scratch on the editor
- cy.insertFormulaFromScratch(this.formulas["formula-general"]);
-
- // Double-click the previous inserted formula to start editing it
- cy.getFormula(0).dblclick();
-
- // Assert that the toolbar is visible so that we know the modal is fully loaded
- cy.get(".wrs_toolbar").should("be.visible");
-
- // Wait for the formula clocked to be loaded
- cy.get(".wrs_container").invoke("text").should("contain", "y"); // .children().should('have.length.at.least', 9);
-
- // Modify the opened formula
- cy.get(".wrs_focusElement").click().type(this.formulas["formula-addition"]);
- // cy.typeInModal('{movetostart}{del}{del}{del}');
-
- // Click the insert button on the mathtype modal to insert the previous edited formula
- cy.clickModalButton("insert");
-
- // Expect the formula to be edited propertly
- cy.getFormula(0)
- .should("have.attr", "alt")
- .then((alt) => {
- expect(alt).to.equal(this.formulas["formula-total-alt-es"]);
- });
-});
diff --git a/cypress/tests/smoke/STD-017.insertion.js b/cypress/tests/smoke/STD-017.insertion.js
deleted file mode 100644
index 769c3e2e3..000000000
--- a/cypress/tests/smoke/STD-017.insertion.js
+++ /dev/null
@@ -1,46 +0,0 @@
-///
-// ***********************************************************
-// Test case: INT-STD-017
-// Title: User edits a formula and cancels the edition.
-// Document: https://docs.google.com/document/d/1CqlEq9p0oVrRhpXjaguehtA4LBl9157qSZ_vg0pdswM/edit
-// Context: E2E / Insertion
-// Issue: KB-13069
-// ***********************************************************
-
-beforeEach(() => {
- // Load fixture data
- cy.fixture("formulas.json").as("formulas");
-
- // Visit the page.
- cy.visit("/");
-
- // Clear the editor content in order to reduce noise
- cy.getTextEditor().clear();
-});
-
-it("should be able to edit and existing formula and cancel the edition", function () {
- // Insert a new MathType formula from scratch on the editor
- cy.insertFormulaFromScratch(this.formulas["formula-general"]);
-
- // Double-click the previous inserted formula to start editing it
- cy.getFormula(0).dblclick();
-
- // Edit the opened formula by adding some other content (=y)
- cy.typeInModal(this.formulas["formula-addition"]);
-
- // Click the cancel button after editing the formula on the mathtype modal
- cy.clickModalButton("cancel");
-
- // CLick the close button on the confirmation close mathtype modal the cancel all changes and close it
- cy.clickModalButton("confirmationClose");
-
- // Assert the formula has no changes
- cy.getFormula(0)
- .should("have.attr", "alt")
- .then((alt) => {
- expect(alt).to.equal(this.formulas["formula-general-alt-es"]);
- });
-
- // Verify the formula is propertly rendered
- cy.getFormula(0).isRendered();
-});
diff --git a/cypress/tests/smoke/STD-019.insertion.js b/cypress/tests/smoke/STD-019.insertion.js
deleted file mode 100644
index 9b43252c8..000000000
--- a/cypress/tests/smoke/STD-019.insertion.js
+++ /dev/null
@@ -1,33 +0,0 @@
-///
-// ***********************************************************
-// Test case: INT-STD-019
-// Title: User edits a latex formula.
-// Document: https://docs.google.com/document/d/1tkYS_g5ZZcjIiUT-nMPv4G2AGlcumw9siTY_B1bFtkE/edit
-// Context: E2E / Insertion
-// Issue: KB-13069
-// ***********************************************************
-beforeEach(() => {
- // Load fixture data
- cy.fixture("formulas.json").as("formulas");
-
- // Visit the page.
- cy.visit("/");
-
- // Clear the editor content in order to reduce noise
- cy.getTextEditor().clear();
-});
-
-it("should be able to edit an existing latex formula", function () {
- // Write a latex formula on the text editor
- cy.typeInTextEditor(this.formulas["latex-general"]);
-
- // Edit the first latex formula with mathtype
- cy.editFormula(this.formulas["latex-addition"], {
- chem: false,
- latex: true,
- formulaId: 0,
- });
-
- // Expect that the text editor contains the latex formula
- cy.getTextEditor().invoke("text").should("contain", "$$\\cos^2(x)+\\sin^2(x)=\\log(e)$$");
-});
diff --git a/cypress/tests/smoke/STD-020.modal.js b/cypress/tests/smoke/STD-020.modal.js
deleted file mode 100644
index 14b85859c..000000000
--- a/cypress/tests/smoke/STD-020.modal.js
+++ /dev/null
@@ -1,23 +0,0 @@
-///
-// ***********************************************************
-// Test case: INT-STD-020
-// Title: User opens MT and closes it via ESC
-// Document: https://docs.google.com/document/d/1v6NyWfvoFrgX7CWufN12_tCzyO9ITvX3HTAXTIYWCSQ/edit
-// Context: E2E / Modal
-// Issue: -
-// ***********************************************************
-beforeEach(() => {
- // Visit page
- cy.visit("/");
-});
-
-it("User opens MT and closes it via ESC", () => {
- // Click the MT button in the HTML editor toolbar
- cy.clickButtonToOpenModal();
-
- // Press the ESC key
- cy.pressESCButton();
-
- // MT editor modal window is closed
- cy.get(".wrs_modal_dialogContainer").should("not.be.visible");
-});
diff --git a/cypress/tests/smoke/STD-021.modal.js b/cypress/tests/smoke/STD-021.modal.js
deleted file mode 100644
index 02076ec3c..000000000
--- a/cypress/tests/smoke/STD-021.modal.js
+++ /dev/null
@@ -1,23 +0,0 @@
-///
-// ***********************************************************
-// Test case: INT-STD-021
-// Title: Open and close the mathtype modal without adding any formula.
-// Document: https://docs.google.com/document/d/1MloNEApADlavZHfODqScyGCNUrZyMgiGYrPx46c4waQ/edit
-// Context: E2E / Modal
-// Issue: KB-13069
-// ***********************************************************
-beforeEach(() => {
- // Visit the page.
- cy.visit("/");
-});
-
-it("should be able to edit and existing formula and cancel the edition", () => {
- // Open the mathtype modal bu clicking the mathtype button
- cy.clickButtonToOpenModal();
-
- // Click the cancel button on the mathtype modal to close the modal
- cy.clickModalButton("cancel");
-
- // Verify the modal is closed
- cy.get(".wrs_focusElement").should("not.be.visible");
-});
diff --git a/cypress/tests/smoke/STD-022.modal.js b/cypress/tests/smoke/STD-022.modal.js
deleted file mode 100644
index 77a8e842e..000000000
--- a/cypress/tests/smoke/STD-022.modal.js
+++ /dev/null
@@ -1,40 +0,0 @@
-///
-// ***********************************************************
-// Test case: INT-STD-022
-// Title: User opens MT, edits an equation and Cancels. Accepts the ‘are you sure you want to leave?’ dialog
-// Document: https://docs.google.com/document/d/11R4j3ZW0a50Lp02frqihtfZPeBFAYfN_xtqrs3AejdM/edit
-// Context: E2E / Modal
-// Issue: -
-// ***********************************************************
-beforeEach(() => {
- // Load fixtures
- cy.fixture("formulas.json").as("formulas");
-
- // Visit page
- cy.visit("/");
-
- // Clear the editor content in order to reduce noise
- cy.getTextEditor().clear();
-});
-
-it("User opens MT, edits an equation and Cancels. Accepts the ‘are you sure you want to leave?’ dialog", function () {
- // Click on the MT icon in the HTML editor toolbar
- cy.clickButtonToOpenModal();
-
- // Type the formula
- cy.typeInModal(this.formulas["formula-general"]);
-
- // Click the Cancel button in the MT editor
- cy.clickModalButton("cancel");
-
- // Click the Close button from the ‘Are you sure you want to leave?’ dialog
- cy.clickModalButton("confirmationClose");
-
- // The MT editor modal window is closed and...
- cy.get(".wrs_modal_dialogContainer").should("not.to.be.visible");
-
- // ... no formula is inserted to the HTML editor
- // We check for the 2nd formula, as currently the demos come with one formula by default
- // (So one .Wirisformula in the editor and one .Wirisformula in the preview)
- cy.get(".Wirisformula").eq(1).should("not.exist");
-});
diff --git a/cypress/tests/smoke/STD-023.modal.js b/cypress/tests/smoke/STD-023.modal.js
deleted file mode 100644
index 583add900..000000000
--- a/cypress/tests/smoke/STD-023.modal.js
+++ /dev/null
@@ -1,40 +0,0 @@
-///
-// ***********************************************************
-// Test case: INT-STD-023
-// Title: User opens MT, edits an equation and closes the modal via X button. Denies the ‘are you sure you want to leave?’ dialog and inserts the formula
-// Document: https://docs.google.com/document/d/1EaC9zB9eIADTk06j3TyPouOwzIp3AHael5zV39kOMyM/edit
-// Context: E2E / Modal
-// Issue: -
-// ***********************************************************
-beforeEach(() => {
- // Load fixtures
- cy.fixture("formulas.json").as("formulas");
-
- // Visit page
- cy.visit("/");
-
- // Clear the editor content in order to reduce noise
- cy.getTextEditor().clear();
-});
-
-it("User opens MT, edits an equation and closes the modal via X button. Denies the ‘are you sure you want to leave?’ dialog and inserts the formula", function () {
- // Click on the MT icon in the HTML editor toolbar
- cy.clickButtonToOpenModal();
-
- // Type the formula
- cy.typeInModal(this.formulas["formula-general"]);
-
- // Click the ‘X’ button of the MT modal window in order to close it
- cy.clickModalButton("xClose");
-
- // Click the Cancel button from the ‘Are you sure you want to leave?’ dialog
- cy.clickModalButton("confirmationCancel");
-
- // Click the Insert button in the MT modal window
- cy.clickModalButton("insert");
-
- // The formula is rendered in the HTML editor
- // We check for the 3rd formula, as currently the demos come with one formula by default
- // (So one .Wirisformula in the editor and one .Wirisformula in the preview)
- cy.getFormula(0).should("be.visible");
-});
diff --git a/cypress/tests/ui/.gitkeep b/cypress/tests/ui/.gitkeep
deleted file mode 100644
index e69de29bb..000000000
diff --git a/cypress/tests/validation/.gitkeep b/cypress/tests/validation/.gitkeep
deleted file mode 100644
index e69de29bb..000000000
diff --git a/demos/html/ckeditor4/project.json b/demos/html/ckeditor4/project.json
index 7810740a5..380718602 100644
--- a/demos/html/ckeditor4/project.json
+++ b/demos/html/ckeditor4/project.json
@@ -51,6 +51,16 @@
"lintFilePatterns": ["demos/html/ckeditor4/**/*.{ts,tsx,js,jsx}"]
},
"outputs": ["{options.outputFile}"]
+ },
+ "serve-static": {
+ "executor": "@nx/web:file-server",
+ "dependsOn": ["build"],
+ "options": {
+ "staticFilePath": "demos/html/ckeditor4/",
+ "port": 8001,
+ "spa": false,
+ "watch": false
+ }
}
}
}
diff --git a/demos/html/ckeditor5/project.json b/demos/html/ckeditor5/project.json
index 689e722fa..5b41a0d58 100644
--- a/demos/html/ckeditor5/project.json
+++ b/demos/html/ckeditor5/project.json
@@ -51,6 +51,16 @@
"lintFilePatterns": ["demos/html/ckeditor5/**/*.{ts,tsx,js,jsx}"]
},
"outputs": ["{options.outputFile}"]
+ },
+ "serve-static": {
+ "executor": "@nx/web:file-server",
+ "dependsOn": ["build"],
+ "options": {
+ "staticFilePath": "demos/html/ckeditor5/",
+ "port": 8002,
+ "spa": false,
+ "watch": false
+ }
}
}
}
diff --git a/demos/html/froala/project.json b/demos/html/froala/project.json
index 8aab2e250..4694ea35e 100644
--- a/demos/html/froala/project.json
+++ b/demos/html/froala/project.json
@@ -51,6 +51,16 @@
"lintFilePatterns": ["demos/html/froala/**/*.{ts,tsx,js,jsx}"]
},
"outputs": ["{options.outputFile}"]
+ },
+ "serve-static": {
+ "executor": "@nx/web:file-server",
+ "dependsOn": ["build"],
+ "options": {
+ "staticFilePath": "demos/html/froala/",
+ "port": 8004,
+ "spa": false,
+ "watch": false
+ }
}
}
}
diff --git a/demos/html/generic/project.json b/demos/html/generic/project.json
index 0d4aabc02..0a24290a8 100644
--- a/demos/html/generic/project.json
+++ b/demos/html/generic/project.json
@@ -51,6 +51,16 @@
"lintFilePatterns": ["demos/html/generic/**/*.{ts,tsx,js,jsx}"]
},
"outputs": ["{options.outputFile}"]
+ },
+ "serve-static": {
+ "executor": "@nx/web:file-server",
+ "dependsOn": ["build"],
+ "options": {
+ "staticFilePath": "demos/html/generic/",
+ "port": 8007,
+ "spa": false,
+ "watch": false
+ }
}
}
}
diff --git a/demos/html/tinymce5/project.json b/demos/html/tinymce5/project.json
index e52f261d8..c55e604eb 100644
--- a/demos/html/tinymce5/project.json
+++ b/demos/html/tinymce5/project.json
@@ -51,6 +51,16 @@
"lintFilePatterns": ["demos/html/tinymce5/**/*.{ts,tsx,js,jsx}"]
},
"outputs": ["{options.outputFile}"]
+ },
+ "serve-static": {
+ "executor": "@nx/web:file-server",
+ "dependsOn": ["build"],
+ "options": {
+ "staticFilePath": "demos/html/tinymce5/",
+ "port": 8006,
+ "spa": false,
+ "watch": false
+ }
}
}
}
diff --git a/demos/html/tinymce6/project.json b/demos/html/tinymce6/project.json
index b64f662b1..0bbc86e0e 100644
--- a/demos/html/tinymce6/project.json
+++ b/demos/html/tinymce6/project.json
@@ -51,6 +51,16 @@
"lintFilePatterns": ["demos/html/tinymce6/**/*.{ts,tsx,js,jsx}"]
},
"outputs": ["{options.outputFile}"]
+ },
+ "serve-static": {
+ "executor": "@nx/web:file-server",
+ "dependsOn": ["build"],
+ "options": {
+ "staticFilePath": "demos/html/tinymce6/",
+ "port": 8008,
+ "spa": false,
+ "watch": false
+ }
}
}
}
diff --git a/demos/html/tinymce7/project.json b/demos/html/tinymce7/project.json
index b6f405d2b..516a47b74 100644
--- a/demos/html/tinymce7/project.json
+++ b/demos/html/tinymce7/project.json
@@ -51,6 +51,16 @@
"lintFilePatterns": ["demos/html/tinymce7/**/*.{ts,tsx,js,jsx}"]
},
"outputs": ["{options.outputFile}"]
+ },
+ "serve-static": {
+ "executor": "@nx/web:file-server",
+ "dependsOn": ["build"],
+ "options": {
+ "staticFilePath": "demos/html/tinymce7/",
+ "port": 8009,
+ "spa": false,
+ "watch": false
+ }
}
}
}
diff --git a/demos/html/tinymce8/project.json b/demos/html/tinymce8/project.json
index 5667ad149..a7da5967a 100644
--- a/demos/html/tinymce8/project.json
+++ b/demos/html/tinymce8/project.json
@@ -51,6 +51,16 @@
"lintFilePatterns": ["demos/html/tinymce8/**/*.{ts,tsx,js,jsx}"]
},
"outputs": ["{options.outputFile}"]
+ },
+ "serve-static": {
+ "executor": "@nx/web:file-server",
+ "dependsOn": ["build"],
+ "options": {
+ "staticFilePath": "demos/html/tinymce8/",
+ "port": 8010,
+ "spa": false,
+ "watch": false
+ }
}
}
}
diff --git a/docs/development/cicd/README.md b/docs/development/cicd/README.md
index 9cdb40d03..5b6296287 100644
--- a/docs/development/cicd/README.md
+++ b/docs/development/cicd/README.md
@@ -15,28 +15,27 @@ This project uses [GitHub actions](https://github.com/features/actions) for the
This job uses JSDoc library to generate a static site as an artifact called `mathtype-html-integration-devkit-docs.zip`, from the comments on the library code.
-### Run Cypress tests with npm packages
+### Run E2E tests
+This workflow runs end-to-end tests across all supported HTML editors using Playwright. The tests are executed in a matrix strategy to enable parallel execution for each editor. See [docs/testing/README.md](../testing/README.md) for further details.
-**[Deprecated]**
+**Supported editors:**
+- Generic HTML
+- CKEditor 4 & 5
+- Froala
+- TinyMCE 5, 6, 7 & 8
-Builds the packages using the source code available at npmjs and runs all available Cypress tests.
+**Key features:**
+- **Parallel execution**: Each editor runs in its own job for faster feedback
+- **Multi-browser testing**: Tests run on Chromium, Firefox, and WebKit
+- **Timeout protection**: Each job has a 30-minute timeout to prevent hanging
+- **Test reporting**: Results are published using JUnit format with detailed reports
+- **Artifact collection**: Test reports are collected as downloadable artifacts
-- **On schedule**: every Monday at 1AM. It sends the test data to [Cypress Dashboard][cypress-dashboard]. It can be run on any branch.
+**Workflow triggers:**
+- Push to master branch
+- Pull requests
+- Manual dispatch
-- **On demand**: a manual trigger that allows the user to send data to [Cypress Dashboard][cypress-dashboard], optionally.
+The workflow builds the necessary packages, starts static file servers for each editor demo, and runs the Playwright test suite against them.
-## Actions secrets
-Secrets are GitHub environment variables that are encrypted. Anyone with collaborator access to this repository can use these secrets for Actions.
-
-| Name | Description |
-| ------------------ | ----------------------------------------------------------------------------------------------------------- |
-| GH_CICD_TOKEN | A GitHub token to allow detecting a build vs a re-run build. [More][cypress-action] |
-| CYPRESS_PROJECT_ID | A 6 character string unique identifier for the project. |
-| CYPRESS_RECORD_KEY | Cypress record key is an authentication key that allows to send record tests data to the Dashboard Service. |
-
-[Visit Secrets page at GitHub][secrets].
-
-[secrets]: https://github.com/wiris/html-integrations/settings/secrets
-[cypress-dashboard]: (https://cypress.io/dashboard/)
-[cypress-action]: https://github.com/cypress-io/github-action
diff --git a/docs/development/testing/README.md b/docs/development/testing/README.md
index 5a129f87e..ef2af6066 100644
--- a/docs/development/testing/README.md
+++ b/docs/development/testing/README.md
@@ -1,52 +1,190 @@
-# Testing
+# E2E Testing Documentation
-[MathType Web Integrations](../../../README.md) → [Documentation](../../README.md) → [Development guide](../README.md) → Testing
+## Overview
-This project uses [Cypress][Cypress] to run integration and validation tests in order to cover all published packages.
+This project uses Playwright for end-to-end testing across multiple HTML editor integrations. The testing setup supports parallel execution across different editors with configurable environments.
-[Cypress]: https://www.cypress.io/
+## Test Structure
-## Table of contents
+The E2E tests are located in `/tests/e2e/` with the following structure:
-- [Run all tests at once](#run-all-tests-at-once)
-- [Run all the tests for a specific demo](#run-all-the-tests-for-a-specific-demo)
+```
+tests/e2e/
+├── .env.example # Environment configuration template
+├── .env # (Optional) Local environment configuration; git-ignored
+├── playwright.config.ts # Playwright configuration
+├── enums/ # Shared enums for test logic
+├── helpers/ # Utility/helper functions
+├── interfaces/ # Shared TypeScript interfaces
+├── page-objects/ # Page object models
+│ └── base_editor.ts # Base editor class
+│ └── html/ # Page object for each editor test page
+└── tests/ # Test specifications
+ ├── edit/ # Formula editing tests
+ │ ├── edit_corner_cases.spec.ts
+ │ ├── edit_hand.spec.ts
+ │ ├── edit_via_doble_click.spec.ts
+ │ └── edit_via_selection.spec.ts
+ ├── editor/ # Editor functionality tests
+ │ ├── copy_cut_drop.spec.ts
+ │ └── editor.spec.ts
+ ├── insert/ # Formula insertion tests
+ │ ├── insert_corner_cases.spec.ts
+ │ ├── insert_hand.spec.ts
+ │ └── insert.spec.ts
+ ├── latex/ # LaTeX functionality tests
+ │ └── latex.spec.ts
+ ├── modal/ # Modal dialog tests
+ │ ├── confirmation_dialog.spec.ts
+ │ └── toolbar.spec.ts
+ └── telemetry/ # Analytics tests
+ └── telemetry.spec.ts
+```
-## Before you begin
+## Supported Editors and Packages
-Linux users will need to install `net-tools` to use Cypress.
+The testing framework supports the following HTML editors with their corresponding localhost ports:
+| Editor | Port | Status |
+|------------|------|--------|
+| ckeditor4 | 8001 | ✅ Active |
+| ckeditor5 | 8002 | ✅ Active |
+| froala | 8004 | ✅ Active |
+| tinymce5 | 8006 | ✅ Active |
+| tinymce6 | 8008 | ✅ Active |
+| tinymce7 | 8009 | ✅ Active |
+| tinymce8 | 8010 | ✅ Active |
+| generic | 8007 | ✅ Active |
+| viewer | ? | 📋 TODO |
-```bash
-$ sudo apt install net-tools
-```
-Also, you will need to allow non-local connections to control the X server on your computer.
+## Environment Configuration
+
+### Local Setup
+You can configure your environment using an optional `.env` file or by setting variables directly in the CLI command, as explained below.
+
+1. **Copy the environment template:**
+ ```bash
+ cp tests/e2e/.env.example tests/e2e/.env
+ ```
+
+2. **Configure your environment:**
+ ```bash
+ # tests/e2e/.env
+
+ # Select editors to test (pipe-separated)
+ HTML_EDITOR=generic|ckeditor4|ckeditor5
-Run this command:
+ # Environment selection
+ USE_STAGING=false
+
+ # Branch for staging tests
+ TEST_BRANCH=master
+
+ # API Keys for commercial editors. Required to deploy the test page.
+ CKEDITOR4_API_KEY=
+ FROALA_API_KEY=
+ ```
+
+### Environment Variables
+
+| Variable | Description | Default | Example | Required |
+|----------|-------------|---------|---------|----------|
+| `HTML_EDITOR` | Pipe-separated list of editors to test | All editors | `generic\|ckeditor5` | Yes |
+| `USE_STAGING` | Use staging environment vs localhost | `false` | `true\|false` | No |
+| `TEST_BRANCH` | Git branch for staging tests | `master` | `feature-branch` | No |
+| `CKEDITOR4_API_KEY` | API key for CKEditor 4 commercial features | None | `your-ckeditor4-key` | For all CKEditor 4 tests |
+| `FROALA_API_KEY` | API key for Froala Editor commercial features | None | `your-froala-key` | For Froala licensed features only |
+
+
+## Running Tests
+
+### Prerequisites
```bash
-$ xhost local:root
+# Install dependencies
+yarn install
+
+# Install Playwright browsers
+yarn playwright install --with-deps
```
-> This has to be executed once after each reboot
+### Local Development
+The `yarn test:e2e` script is defined in the main package.json and runs the E2E tests.
-## Run all tests at once
+Playwright is configured to pre-build and deploy both the package and test site (`demos` folder) for the configured
+editors and deploy them in order to run the test. Don't pre-deploy the test page, Playwright will do it by itself.
-Before running the tests you will need to build all package and start all demos.
+```bash
+# Run tests for specific editors
+HTML_EDITOR=ckeditor5 yarn test:e2e
-All tests can be run with the commands:
+# Run tests for multiple editors
+HTML_EDITOR=generic|froala yarn test:e2e
-```sh
-$ nx run-many --target=start --all --parallel
-$ nx run-many --target=test --all --parallel
-```
+# Run all tests for all editors. If no HTML_EDITOR variable is set, all editors are tested
+yarn test:e2e
-## Run all the tests for a specific demo
+# Run with staging environment
+USE_STAGING=true yarn test:e2e
-You can run all tests for a specific demo with the `nx test ` command.
+# Run specific browser
+yarn test:e2e --project=webkit
-Before running the tests you will need to build the package and start a demo. For example to run all tests on the `ckeditor5` demo run:
+# Run in headed mode
+yarn test:e2e --headed
+# Run specific test file
+yarn test:e2e tests/insert/insert.spec.ts
```
-$ nx start html-ckeditor5
-$ nx test ckeditor5
+[See the official Playwright CLI documentation](https://playwright.dev/docs/test-cli) for more details on available commands and options.
+
+
+**Example workflow:**
+```bash
+# Build and test CKEditor5
+yarn
+HTML_EDITOR=ckeditor5 yarn test:e2e
```
+
+## Playwright Configuration
+See ([`playwright.config.ts`](../../../tests/e2e/playwright.config.ts)).
+
+
+## CI/CD Integration
+
+### GitHub Actions Workflow
+
+The E2E tests are automated via GitHub Actions ([`run-e2e-tests.yml`](../../../.github/workflows/cypress-Run-tests-with-npm-packages.yml)):
+
+- **When tests run**: On pushes to `main`, pull requests, and manual workflow dispatch
+- **Parallelization**: Each editor runs in a separate job using matrix strategy for maximum parallel execution
+- **Reports**:
+ - Github reports appear in the GitHub Actions **Checks** tab
+ - Failed tests create GitHub Actions annotations with direct links for quick debugging.
+ - A single HTML report is generated and attached to the workflow run. Contains results for all Editors.
+
+## Test Coverage
+
+| Test File | Category | Description |
+|-----------|----------|-------------|
+| `edit/edit_corner_cases.spec.ts` | Edit | Edge cases and error conditions in formula editing |
+| `edit/edit_hand.spec.ts` | Edit | Manual formula modifications and handwriting input |
+| `edit/edit_via_doble_click.spec.ts` | Edit | Editing formulas by double-clicking on existing formulas |
+| `edit/edit_via_selection.spec.ts` | Edit | Editing formulas via text selection and context menu |
+| `editor/copy_cut_drop.spec.ts` | Editor | Clipboard operations (copy/cut/paste) and drag-drop functionality |
+| `editor/editor.spec.ts` | Editor | General editor behavior and integration tests |
+| `insert/insert_corner_cases.spec.ts` | Insert | Edge cases and error conditions in formula insertion |
+| `insert/insert_hand.spec.ts` | Insert | Manual formula creation via handwriting input |
+| `insert/insert.spec.ts` | Insert | Standard formula insertion workflows and toolbar interactions |
+| `latex/latex.spec.ts` | LaTeX | LaTeX rendering, parsing, and conversion functionality |
+| `modal/confirmation_dialog.spec.ts` | Modal | Confirmation dialog interactions and user workflows |
+| `modal/toolbar.spec.ts` | Modal | Toolbar modal functionality and behavior |
+| `telemetry/telemetry.spec.ts` | Telemetry | Usage metrics, event tracking, and analytics validation |
+
+
+# TODOs
+This project previously used cypress for E2E testing. There might still be some reference to Cypress in the code (e.g.: see test section in the demos `project.json` files). These must be deleteded.
+- Remove cypress refereces in the `project.json` files
+- Remove cypress dashboard secrets in the repository
+- Remove old documentation cypress references.
+
diff --git a/package.json b/package.json
index c2a3b8f2c..87eb9274f 100644
--- a/package.json
+++ b/package.json
@@ -9,8 +9,7 @@
"preinstall": "node packages/res/git-data.mjs",
"postinstall": "rm -rf ~/.config/yarn/link/* && for d in packages/*/ ; do (cd $d && yarn link); done",
"test-old": "node scripts/services/executeTests.js",
- "test": "nx run-many --target=test --all --parallel",
- "test:ci": "docker run -v $PWD:/cypress --net=host -w /cypress -e CYPRESS_PROJECT_ID --entrypoint=cypress cypress/included:7.5.0 run --project .",
+ "test:e2e": "playwright test --config=tests/e2e/playwright.config.ts",
"build": "cd demos/html/generic && npm install && npm start &",
"lint": "prettier --write . --ignore-path .gitignore --ignore-path .prettierignore --ignore-path .eslintignore && nx run-many --target=lint --all --parallel --fix"
},
@@ -18,22 +17,23 @@
"@babel/eslint-parser": "^7.24.1",
"@nrwl/js": "18.2.2",
"@nrwl/tao": "18.2.2",
- "@nx/cypress": "18.2.2",
"@nx/eslint-plugin": "18.2.2",
"@nx/linter": "18.2.2",
"@nx/web": "18.2.2",
"@nx/webpack": "18.2.2",
"@nx/workspace": "18.2.2",
+ "@playwright/test": "^1.40.0",
"@types/node": "20.12.4",
"clean-webpack-plugin": "^4.0.0",
+ "dotenv": "^16.3.1",
"eslint": "^8.57.0",
"eslint-config-airbnb-base": "^15.0.0",
+ "eslint-config-prettier": "^10.0.1",
"eslint-plugin-import": "^2.29.0",
"eslint-plugin-jsdoc": "^48.2.3",
+ "eslint-plugin-prettier": "^5.2.3",
"html-validate": "^8.18.1",
"nx": "18.2.2",
- "eslint-config-prettier": "^10.0.1",
- "eslint-plugin-prettier": "^5.2.3",
"prettier": "3.5.2",
"typescript": "~5.4.4"
},
@@ -43,5 +43,8 @@
"packages/**",
"demos/**"
],
- "packageManager": "yarn@1.22.22+sha1.ac34549e6aa8e7ead463a7407e1c7390f61a6610"
+ "packageManager": "yarn@1.22.22+sha1.ac34549e6aa8e7ead463a7407e1c7390f61a6610",
+ "dependencies": {
+ "serve": "^14.2.5"
+ }
}
diff --git a/packages/ckeditor5/dist/browser/index.css.map b/packages/ckeditor5/dist/browser/index.css.map
index 405bbbbb5..dc4759a7c 100644
--- a/packages/ckeditor5/dist/browser/index.css.map
+++ b/packages/ckeditor5/dist/browser/index.css.map
@@ -1 +1 @@
-{"version":3,"sources":["../../../devkit/styles/styles.css","../../theme/styles.css"],"names":[],"mappings":"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","file":"index.css","sourcesContent":[".wrs_modal_overlay {\n position: fixed;\n font-family: arial, sans-serif;\n top: 0;\n right: 0;\n left: 0;\n bottom: 0;\n background: rgba(0, 0, 0, 0.8);\n z-index: 999998;\n opacity: 0.65;\n pointer-events: auto;\n}\n\n.wrs_modal_overlay.wrs_modal_ios {\n visibility: hidden;\n display: none;\n}\n\n.wrs_modal_overlay.wrs_modal_android {\n visibility: hidden;\n display: none;\n}\n\n.wrs_modal_overlay.wrs_modal_ios.moodle {\n position: fixed;\n}\n\n.wrs_modal_overlay.wrs_modal_desktop.wrs_stack {\n background: rgba(0, 0, 0, 0);\n display: none;\n}\n\n.wrs_modal_overlay.wrs_modal_desktop.wrs_maximized {\n background: rgba(0, 0, 0, 0.8);\n}\n\n.wrs_modal_overlay.wrs_modal_desktop.wrs_minimized {\n background: rgba(0, 0, 0, 0);\n display: none;\n}\n\n.wrs_modal_overlay.wrs_modal_desktop.wrs_closed {\n background: rgba(0, 0, 0, 0);\n display: none;\n}\n\n.wrs_modal_title {\n color: #fff;\n padding: 5px 0 5px 10px;\n -moz-user-select: none;\n -webkit-user-select: none;\n -ms-user-select: none;\n user-select: none;\n text-align: left;\n}\n\n.wrs_modal_close_button {\n float: right;\n cursor: pointer;\n color: #fff;\n padding: 5px 10px 5px 0;\n margin: 10px 7px 0 0;\n background-repeat: no-repeat;\n}\n\n.wrs_modal_minimize_button {\n float: right;\n cursor: pointer;\n color: #fff;\n padding: 5px 10px 5px 0;\n top: inherit;\n margin: 10px 7px 0 0;\n}\n\n.wrs_modal_stack_button {\n float: right;\n cursor: pointer;\n color: #fff;\n margin: 10px 7px 0 0;\n padding: 5px 10px 5px 0;\n top: inherit;\n}\n\n.wrs_modal_stack_button.wrs_stack {\n visibility: hidden;\n margin: 0;\n padding: 0;\n}\n\n.wrs_modal_stack_button.wrs_minimized {\n visibility: hidden;\n margin: 0;\n padding: 0;\n}\n\n.wrs_modal_maximize_button {\n float: right;\n cursor: pointer;\n color: #fff;\n margin: 10px 7px 0 0;\n padding: 5px 10px 5px 0;\n top: inherit;\n}\n\n.wrs_modal_maximize_button.wrs_maximized {\n visibility: hidden;\n margin: 0;\n padding: 0;\n}\n\n.wrs_modal_title_bar {\n display: block;\n background-color: #778e9a;\n}\n\n.wrs_modal_dialogContainer {\n border: none;\n background: #fafafa;\n z-index: 999999;\n}\n\n.wrs_modal_dialogContainer.wrs_modal_desktop {\n font-size: 14px;\n display: flex;\n flex-flow: column;\n}\n\n.wrs_modal_dialogContainer.wrs_modal_desktop.wrs_maximized {\n position: fixed;\n}\n\n.wrs_modal_dialogContainer.wrs_modal_desktop.wrs_minimized {\n position: fixed;\n top: inherit;\n margin: 0;\n margin-right: 10px;\n}\n\n.wrs_modal_dialogContainer.wrs_closed {\n visibility: hidden;\n display: none;\n opacity: 0;\n}\n\n/* Class that exists but hasn't got css properties defined\n.wrs_modal_dialogContainer.wrs_modal_desktop.wrs_minimized.wrs_drag {} */\n\n.wrs_modal_dialogContainer.wrs_modal_desktop.wrs_stack {\n position: fixed;\n bottom: 0;\n right: 0;\n box-shadow: rgba(0, 0, 0, 0.5) 0 2px 8px;\n}\n\n.wrs_modal_dialogContainer.wrs_drag {\n box-shadow: rgba(0, 0, 0, 0.5) 0 2px 8px;\n}\n\n.wrs_modal_dialogContainer.wrs_modal_desktop.wrs_drag {\n box-shadow: rgba(0, 0, 0, 0.5) 0 2px 8px;\n}\n\n.wrs_modal_dialogContainer.wrs_modal_ios,\n.wrs_modal_dialogContainer.wrs_modal_android {\n margin: 0px;\n position: fixed;\n height: auto;\n overflow: hidden;\n top: calc(env(safe-area-inset-top));\n right: calc(env(safe-area-inset-right));\n left: calc(env(safe-area-inset-left));\n bottom: 0px;\n transform: none;\n box-sizing: border-box;\n display: flex;\n flex-direction: column;\n}\n\n/* Class that exists but hasn't got css properties defined\n.wrs_content_container.wrs_maximized {} */\n\n.wrs_content_container.wrs_minimized {\n display: none;\n}\n\n/* .wrs_editor {\n flex-grow: 1;\n} */\n\n.wrs_content_container.wrs_modal_desktop > div:first-child {\n flex-grow: 1;\n}\n\n.wrs_modal_wrapper.wrs_modal_android,\n.wrs_modal_wrapper.wrs_modal_ios {\n margin: 0.5rem !important;\n flex-grow: 0.98;\n display: flex;\n}\n\n.wrs_content_container.wrs_modal_ios,\n.wrs_content_container.wrs_modal_android {\n flex-grow: 0.98;\n display: flex;\n}\n\n.wrs_content_container.wrs_modal_desktop {\n width: 100%;\n flex-grow: 1;\n display: flex;\n flex-direction: column;\n height: auto !important;\n}\n\n.wrs_modal_dialogContainer.wrs_modal_badStock {\n width: 100%;\n height: 280px;\n margin: 0 auto;\n border-width: 0;\n}\n\n.wrs_modal_wrapper.wrs_modal_badStock {\n width: 100%;\n height: 280px;\n margin: 0 auto;\n border-width: 0;\n}\n\n.wrs_noselect {\n -moz-user-select: none;\n -khtml-user-select: none;\n -webkit-user-select: none;\n -ms-user-select: none;\n user-select: none;\n}\n\n.wrs_bottom_right_resizer {\n width: 10px;\n height: 10px;\n color: #778e9a;\n position: absolute;\n right: 4px;\n bottom: 8px;\n cursor: se-resize;\n -moz-user-select: none;\n -webkit-user-select: none;\n -ms-user-select: none;\n user-select: none;\n}\n\n.wrs_bottom_left_resizer {\n width: 15px;\n height: 15px;\n color: #778e9a;\n position: absolute;\n left: 0;\n top: 0;\n cursor: se-resize;\n}\n\n.wrs_modal_controls {\n height: 42px;\n line-height: normal;\n}\n\n.wrs_modal_links {\n margin: 10px auto;\n margin-bottom: 0;\n font-family: arial, sans-serif;\n padding: 6px;\n display: inline;\n float: right;\n text-align: right;\n}\n\n.wrs_modal_links > a {\n text-decoration: none;\n color: #778e9a;\n font-size: 16px;\n}\n\n.wrs_modal_button_cancel,\n.wrs_modal_button_cancel:hover,\n.wrs_modal_button_cancel:visited,\n.wrs_modal_button_cancel:active,\n.wrs_modal_button_cancel:focus {\n min-width: 80px;\n font-size: 14px;\n border-radius: 3px;\n border: 1px solid #778e9a;\n padding: 6px 8px;\n margin: 10px auto;\n margin-left: 5px;\n margin-bottom: 0;\n cursor: pointer;\n font-family: arial, sans-serif;\n background-color: #ddd;\n height: 32px;\n}\n\n.wrs_modal_button_accept,\n.wrs_modal_button_accept:hover,\n.wrs_modal_button_accept:visited,\n.wrs_modal_button_accept:active,\n.wrs_modal_button_accept:focus {\n min-width: 80px;\n font-size: 14px;\n border-radius: 3px;\n border: 1px solid #778e9a;\n padding: 6px 8px;\n margin: 10px auto;\n margin-right: 5px;\n margin-bottom: 0;\n color: #fff;\n background: #778e9a;\n cursor: pointer;\n font-family: arial, sans-serif;\n height: 32px;\n}\n\n.wrs_editor_vertical_bar {\n height: 20px;\n float: right;\n background: none;\n width: 20px;\n cursor: pointer;\n}\n\n.wrs_modal_buttons_container {\n display: inline;\n float: left;\n}\n\n.wrs_modal_buttons_container.wrs_modalDesktop {\n padding-left: 0;\n}\n\n.wrs_modal_buttons_container > button {\n line-height: normal;\n background-image: none;\n}\n\n.wrs_modal_wrapper {\n margin: 6px;\n display: flex;\n flex-direction: column;\n flex-grow: 1;\n}\n\n.wrs_modal_wrapper.wrs_modal_desktop.wrs_minimized {\n display: none;\n}\n\n.wrs_popupmessage_overlay_envolture {\n display: none;\n width: 100%;\n}\n\n.wrs_popupmessage_overlay {\n position: absolute;\n width: 100%;\n height: 100%;\n top: 0;\n left: 0;\n right: 0;\n bottom: 0;\n background-color: rgba(0, 0, 0, 0.5);\n z-index: 4;\n cursor: pointer;\n}\n\n.wrs_popupmessage_panel {\n top: 50%;\n left: 50%;\n transform: translate(-50%, -50%);\n position: absolute;\n background: white;\n max-width: 500px;\n width: 75%;\n border-radius: 2px;\n padding: 20px;\n font-family: sans-serif;\n font-size: 15px;\n text-align: left;\n color: #2e2e2e;\n z-index: 5;\n max-height: 75%;\n overflow: auto;\n}\n\n.wrs_popupmessage_button_area {\n margin: 10px 0 0 0;\n}\n\n.wrs_panelContainer * {\n border: 0;\n}\n\n.wrs_button_cancel,\n.wrs_button_cancel:hover,\n.wrs_button_cancel:visited,\n.wrs_button_cancel:active,\n.wrs_button_cancel:focus {\n min-width: 80px;\n font-size: 14px;\n border-radius: 3px;\n border: 1px solid #778e9a;\n padding: 6px 8px;\n margin: 10px auto;\n margin-left: 5px;\n margin-bottom: 0;\n cursor: pointer;\n font-family: arial, sans-serif;\n background-color: #ddd;\n background-image: none;\n height: 32px;\n}\n\n.wrs_button_accept,\n.wrs_button_accept:hover,\n.wrs_button_accept:visited,\n.wrs_button_accept:active,\n.wrs_button_accept:focus {\n min-width: 80px;\n font-size: 14px;\n border-radius: 3px;\n border: 1px solid #778e9a;\n padding: 6px 8px;\n margin: 10px auto;\n margin-right: 5px;\n margin-bottom: 0;\n color: #fff;\n background: #778e9a;\n cursor: pointer;\n font-family: arial, sans-serif;\n height: 32px;\n}\n\n.wrs_editor button {\n box-shadow: none;\n}\n\n.wrs_editor .wrs_header button {\n border-bottom: none;\n border-bottom-left-radius: 0;\n border-bottom-right-radius: 0;\n}\n\n.wrs_modal_overlay.wrs_modal_desktop.wrs_stack.wrs_overlay_active {\n display: block;\n}\n\n/* Fix selection in drupal style */\n.wrs_toolbar tr:focus {\n background: none;\n}\n\n.wrs_toolbar tr:hover {\n background: none;\n}\n\n/* End of fix drupal */\n.wrs_modal_rtl .wrs_modal_button_cancel {\n margin-right: 5px;\n margin-left: 0;\n}\n\n.wrs_modal_rtl .wrs_modal_button_accept {\n margin-right: 0;\n margin-left: 5px;\n}\n\n.wrs_modal_rtl .wrs_button_cancel {\n margin-right: 5px;\n margin-left: 0;\n}\n\n.wrs_modal_rtl .wrs_button_accept {\n margin-right: 0;\n margin-left: 5px;\n}\n\n/* The Offline Modal (background) */\n.wrs_modal_offline {\n display: none;\n /* Hidden by default */\n position: fixed;\n /* Stay in place */\n z-index: 2;\n /* Sit on top */\n padding-top: 150px;\n /* Location of the box */\n left: 0;\n top: 0;\n width: 100%;\n /* Full width */\n height: 100%;\n /* Full height */\n overflow: auto;\n /* Enable scroll if needed */\n background-color: rgb(0, 0, 0);\n /* Fallback color */\n background-color: rgba(0, 0, 0, 0.4);\n /* Black w/ opacity */\n margin: auto;\n}\n\n/* Modal Content */\n.wrs_modal_content_offline {\n margin: auto;\n padding: 16px;\n background: #fff7ed;\n border-radius: 6px;\n width: 517px;\n height: 100px;\n position: relative;\n}\n\n/* The Close Button */\n.wrs_modal_offline_close {\n color: #c2410c;\n font-size: 24px;\n font-weight: bold;\n left: 95.7%;\n right: 2.08%;\n top: 7.6%;\n bottom: 75%;\n font-family: \"Inter\";\n font-style: normal;\n font-weight: 400;\n position: absolute;\n}\n\n/* The Warn Icon */\n.wrs_modal_offline_warn {\n position: absolute;\n left: 2.08%;\n right: 94%;\n top: 11.6%;\n bottom: 75%;\n font-size: 24px;\n color: #fb923c;\n background-repeat: no-repeat;\n}\n\n.wrs_modal_offline_text_container {\n position: absolute;\n left: 6.8%;\n right: 6.08%;\n top: 10.4%;\n bottom: 2%;\n}\n\n.wrs_modal_offline_text {\n font-family: \"Inter\";\n font-style: normal;\n font-weight: 400;\n font-size: 14px;\n line-height: 20px;\n color: #c2410c;\n}\n\n.wrs_modal_offline_text_warn {\n height: 25px;\n font-family: \"Inter\";\n font-style: normal;\n font-size: 14px;\n line-height: 20px;\n font-weight: bold;\n color: #c2410c;\n}\n\n.wrs_modal_offline_close:hover,\n.wrs_modal_offline_close:focus {\n color: #000;\n text-decoration: none;\n cursor: pointer;\n}\n","/* Replace the default suggestion marker border because it's doesn't span the entire widget. */\n.ck-widget.ck-math-widget.ck-suggestion-marker {\n border: none;\n\n &.ck-suggestion-marker-insertion {\n & .Wirisformula {\n border: 3px solid var(--ck-color-suggestion-marker-insertion-border);\n }\n\n &.ck-suggestion-marker--active {\n & .Wirisformula {\n border: 3px solid var(--ck-color-suggestion-marker-insertion-border-active);\n }\n }\n }\n\n &.ck-suggestion-marker-deletion {\n & .Wirisformula {\n border: 3px solid var(--ck-color-suggestion-marker-deletion-border);\n }\n\n &.ck-suggestion-marker--active {\n & .Wirisformula {\n border: 3px solid var(--ck-color-suggestion-marker-deletion-border-active);\n }\n }\n }\n}\n"]}
\ No newline at end of file
+{"version":3,"sources":["../../node_modules/@wiris/mathtype-html-integration-devkit/styles/styles.css","../../theme/styles.css"],"names":[],"mappings":"AAAA;EACE,eAAe;EACf,8BAA8B;EAC9B,MAAM;EACN,QAAQ;EACR,OAAO;EACP,SAAS;EACT,8BAA8B;EAC9B,eAAe;EACf,aAAa;EACb,oBAAoB;AACtB;;AAEA;EACE,kBAAkB;EAClB,aAAa;AACf;;AAEA;EACE,kBAAkB;EAClB,aAAa;AACf;;AAEA;EACE,eAAe;AACjB;;AAEA;EACE,4BAA4B;EAC5B,aAAa;AACf;;AAEA;EACE,8BAA8B;AAChC;;AAEA;EACE,4BAA4B;EAC5B,aAAa;AACf;;AAEA;EACE,4BAA4B;EAC5B,aAAa;AACf;;AAEA;EACE,WAAW;EACX,uBAAuB;EACvB,sBAAsB;EACtB,yBAAyB;EACzB,qBAAqB;EACrB,iBAAiB;EACjB,gBAAgB;AAClB;;AAEA;EACE,YAAY;EACZ,eAAe;EACf,WAAW;EACX,uBAAuB;EACvB,oBAAoB;EACpB,4BAA4B;AAC9B;;AAEA;EACE,YAAY;EACZ,eAAe;EACf,WAAW;EACX,uBAAuB;EACvB,YAAY;EACZ,oBAAoB;AACtB;;AAEA;EACE,YAAY;EACZ,eAAe;EACf,WAAW;EACX,oBAAoB;EACpB,uBAAuB;EACvB,YAAY;AACd;;AAEA;EACE,kBAAkB;EAClB,SAAS;EACT,UAAU;AACZ;;AAEA;EACE,kBAAkB;EAClB,SAAS;EACT,UAAU;AACZ;;AAEA;EACE,YAAY;EACZ,eAAe;EACf,WAAW;EACX,oBAAoB;EACpB,uBAAuB;EACvB,YAAY;AACd;;AAEA;EACE,kBAAkB;EAClB,SAAS;EACT,UAAU;AACZ;;AAEA;EACE,cAAc;EACd,yBAAyB;AAC3B;;AAEA;EACE,YAAY;EACZ,mBAAmB;EACnB,eAAe;AACjB;;AAEA;EACE,eAAe;EACf,aAAa;EACb,iBAAiB;AACnB;;AAEA;EACE,eAAe;AACjB;;AAEA;EACE,eAAe;EACf,YAAY;EACZ,SAAS;EACT,kBAAkB;AACpB;;AAEA;EACE,kBAAkB;EAClB,aAAa;EACb,UAAU;AACZ;;AAEA;wEACwE;;AAExE;EACE,eAAe;EACf,SAAS;EACT,QAAQ;EACR,wCAAwC;AAC1C;;AAEA;EACE,wCAAwC;AAC1C;;AAEA;EACE,wCAAwC;AAC1C;;AAEA;;EAEE,WAAW;EACX,eAAe;EACf,YAAY;EACZ,gBAAgB;EAChB,mCAAmC;EACnC,uCAAuC;EACvC,qCAAqC;EACrC,WAAW;EACX,eAAe;EACf,sBAAsB;EACtB,aAAa;EACb,sBAAsB;AACxB;;AAEA;yCACyC;;AAEzC;EACE,aAAa;AACf;;AAEA;;GAEG;;AAEH;EACE,YAAY;AACd;;AAEA;;EAEE,yBAAyB;EACzB,eAAe;EACf,aAAa;AACf;;AAEA;;EAEE,eAAe;EACf,aAAa;AACf;;AAEA;EACE,WAAW;EACX,YAAY;EACZ,aAAa;EACb,sBAAsB;EACtB,uBAAuB;AACzB;;AAEA;EACE,WAAW;EACX,aAAa;EACb,cAAc;EACd,eAAe;AACjB;;AAEA;EACE,WAAW;EACX,aAAa;EACb,cAAc;EACd,eAAe;AACjB;;AAEA;EACE,sBAAsB;EACtB,wBAAwB;EACxB,yBAAyB;EACzB,qBAAqB;EACrB,iBAAiB;AACnB;;AAEA;EACE,WAAW;EACX,YAAY;EACZ,cAAc;EACd,kBAAkB;EAClB,UAAU;EACV,WAAW;EACX,iBAAiB;EACjB,sBAAsB;EACtB,yBAAyB;EACzB,qBAAqB;EACrB,iBAAiB;AACnB;;AAEA;EACE,WAAW;EACX,YAAY;EACZ,cAAc;EACd,kBAAkB;EAClB,OAAO;EACP,MAAM;EACN,iBAAiB;AACnB;;AAEA;EACE,YAAY;EACZ,mBAAmB;AACrB;;AAEA;EACE,iBAAiB;EACjB,gBAAgB;EAChB,8BAA8B;EAC9B,YAAY;EACZ,eAAe;EACf,YAAY;EACZ,iBAAiB;AACnB;;AAEA;EACE,qBAAqB;EACrB,cAAc;EACd,eAAe;AACjB;;AAEA;;;;;EAKE,eAAe;EACf,eAAe;EACf,kBAAkB;EAClB,yBAAyB;EACzB,gBAAgB;EAChB,iBAAiB;EACjB,gBAAgB;EAChB,gBAAgB;EAChB,eAAe;EACf,8BAA8B;EAC9B,sBAAsB;EACtB,YAAY;AACd;;AAEA;;;;;EAKE,eAAe;EACf,eAAe;EACf,kBAAkB;EAClB,yBAAyB;EACzB,gBAAgB;EAChB,iBAAiB;EACjB,iBAAiB;EACjB,gBAAgB;EAChB,WAAW;EACX,mBAAmB;EACnB,eAAe;EACf,8BAA8B;EAC9B,YAAY;AACd;;AAEA;EACE,YAAY;EACZ,YAAY;EACZ,gBAAgB;EAChB,WAAW;EACX,eAAe;AACjB;;AAEA;EACE,eAAe;EACf,WAAW;AACb;;AAEA;EACE,eAAe;AACjB;;AAEA;EACE,mBAAmB;EACnB,sBAAsB;AACxB;;AAEA;EACE,WAAW;EACX,aAAa;EACb,sBAAsB;EACtB,YAAY;AACd;;AAEA;EACE,aAAa;AACf;;AAEA;EACE,aAAa;EACb,WAAW;AACb;;AAEA;EACE,kBAAkB;EAClB,WAAW;EACX,YAAY;EACZ,MAAM;EACN,OAAO;EACP,QAAQ;EACR,SAAS;EACT,oCAAoC;EACpC,UAAU;EACV,eAAe;AACjB;;AAEA;EACE,QAAQ;EACR,SAAS;EACT,gCAAgC;EAChC,kBAAkB;EAClB,iBAAiB;EACjB,gBAAgB;EAChB,UAAU;EACV,kBAAkB;EAClB,aAAa;EACb,uBAAuB;EACvB,eAAe;EACf,gBAAgB;EAChB,cAAc;EACd,UAAU;EACV,eAAe;EACf,cAAc;AAChB;;AAEA;EACE,kBAAkB;AACpB;;AAEA;EACE,SAAS;AACX;;AAEA;;;;;EAKE,eAAe;EACf,eAAe;EACf,kBAAkB;EAClB,yBAAyB;EACzB,gBAAgB;EAChB,iBAAiB;EACjB,gBAAgB;EAChB,gBAAgB;EAChB,eAAe;EACf,8BAA8B;EAC9B,sBAAsB;EACtB,sBAAsB;EACtB,YAAY;AACd;;AAEA;;;;;EAKE,eAAe;EACf,eAAe;EACf,kBAAkB;EAClB,yBAAyB;EACzB,gBAAgB;EAChB,iBAAiB;EACjB,iBAAiB;EACjB,gBAAgB;EAChB,WAAW;EACX,mBAAmB;EACnB,eAAe;EACf,8BAA8B;EAC9B,YAAY;AACd;;AAEA;EACE,gBAAgB;AAClB;;AAEA;EACE,mBAAmB;EACnB,4BAA4B;EAC5B,6BAA6B;AAC/B;;AAEA;EACE,cAAc;AAChB;;AAEA,kCAAkC;AAClC;EACE,gBAAgB;AAClB;;AAEA;EACE,gBAAgB;AAClB;;AAEA,sBAAsB;AACtB;EACE,iBAAiB;EACjB,cAAc;AAChB;;AAEA;EACE,eAAe;EACf,gBAAgB;AAClB;;AAEA;EACE,iBAAiB;EACjB,cAAc;AAChB;;AAEA;EACE,eAAe;EACf,gBAAgB;AAClB;;AAEA,mCAAmC;AACnC;EACE,aAAa;EACb,sBAAsB;EACtB,eAAe;EACf,kBAAkB;EAClB,UAAU;EACV,eAAe;EACf,kBAAkB;EAClB,wBAAwB;EACxB,OAAO;EACP,MAAM;EACN,WAAW;EACX,eAAe;EACf,YAAY;EACZ,gBAAgB;EAChB,cAAc;EACd,4BAA4B;EAC5B,8BAA8B;EAC9B,mBAAmB;EACnB,oCAAoC;EACpC,qBAAqB;EACrB,YAAY;AACd;;AAEA,kBAAkB;AAClB;EACE,YAAY;EACZ,aAAa;EACb,mBAAmB;EACnB,kBAAkB;EAClB,YAAY;EACZ,aAAa;EACb,kBAAkB;AACpB;;AAEA,qBAAqB;AACrB;EACE,cAAc;EACd,eAAe;EACf,iBAAiB;EACjB,WAAW;EACX,YAAY;EACZ,SAAS;EACT,WAAW;EACX,oBAAoB;EACpB,kBAAkB;EAClB,gBAAgB;EAChB,kBAAkB;AACpB;;AAEA,kBAAkB;AAClB;EACE,kBAAkB;EAClB,WAAW;EACX,UAAU;EACV,UAAU;EACV,WAAW;EACX,eAAe;EACf,cAAc;EACd,4BAA4B;AAC9B;;AAEA;EACE,kBAAkB;EAClB,UAAU;EACV,YAAY;EACZ,UAAU;EACV,UAAU;AACZ;;AAEA;EACE,oBAAoB;EACpB,kBAAkB;EAClB,gBAAgB;EAChB,eAAe;EACf,iBAAiB;EACjB,cAAc;AAChB;;AAEA;EACE,YAAY;EACZ,oBAAoB;EACpB,kBAAkB;EAClB,eAAe;EACf,iBAAiB;EACjB,iBAAiB;EACjB,cAAc;AAChB;;AAEA;;EAEE,WAAW;EACX,qBAAqB;EACrB,eAAe;AACjB;;ACjkBA,8FAA8F;AAC9F;EACE,YAAY;AAyBd;AAtBI;MACE,oEAAoE;IACtE;AAGE;QACE,2EAA2E;MAC7E;AAKF;MACE,mEAAmE;IACrE;AAGE;QACE,0EAA0E;MAC5E","file":"index.css","sourcesContent":[".wrs_modal_overlay {\n position: fixed;\n font-family: arial, sans-serif;\n top: 0;\n right: 0;\n left: 0;\n bottom: 0;\n background: rgba(0, 0, 0, 0.8);\n z-index: 999998;\n opacity: 0.65;\n pointer-events: auto;\n}\n\n.wrs_modal_overlay.wrs_modal_ios {\n visibility: hidden;\n display: none;\n}\n\n.wrs_modal_overlay.wrs_modal_android {\n visibility: hidden;\n display: none;\n}\n\n.wrs_modal_overlay.wrs_modal_ios.moodle {\n position: fixed;\n}\n\n.wrs_modal_overlay.wrs_modal_desktop.wrs_stack {\n background: rgba(0, 0, 0, 0);\n display: none;\n}\n\n.wrs_modal_overlay.wrs_modal_desktop.wrs_maximized {\n background: rgba(0, 0, 0, 0.8);\n}\n\n.wrs_modal_overlay.wrs_modal_desktop.wrs_minimized {\n background: rgba(0, 0, 0, 0);\n display: none;\n}\n\n.wrs_modal_overlay.wrs_modal_desktop.wrs_closed {\n background: rgba(0, 0, 0, 0);\n display: none;\n}\n\n.wrs_modal_title {\n color: #fff;\n padding: 5px 0 5px 10px;\n -moz-user-select: none;\n -webkit-user-select: none;\n -ms-user-select: none;\n user-select: none;\n text-align: left;\n}\n\n.wrs_modal_close_button {\n float: right;\n cursor: pointer;\n color: #fff;\n padding: 5px 10px 5px 0;\n margin: 10px 7px 0 0;\n background-repeat: no-repeat;\n}\n\n.wrs_modal_minimize_button {\n float: right;\n cursor: pointer;\n color: #fff;\n padding: 5px 10px 5px 0;\n top: inherit;\n margin: 10px 7px 0 0;\n}\n\n.wrs_modal_stack_button {\n float: right;\n cursor: pointer;\n color: #fff;\n margin: 10px 7px 0 0;\n padding: 5px 10px 5px 0;\n top: inherit;\n}\n\n.wrs_modal_stack_button.wrs_stack {\n visibility: hidden;\n margin: 0;\n padding: 0;\n}\n\n.wrs_modal_stack_button.wrs_minimized {\n visibility: hidden;\n margin: 0;\n padding: 0;\n}\n\n.wrs_modal_maximize_button {\n float: right;\n cursor: pointer;\n color: #fff;\n margin: 10px 7px 0 0;\n padding: 5px 10px 5px 0;\n top: inherit;\n}\n\n.wrs_modal_maximize_button.wrs_maximized {\n visibility: hidden;\n margin: 0;\n padding: 0;\n}\n\n.wrs_modal_title_bar {\n display: block;\n background-color: #778e9a;\n}\n\n.wrs_modal_dialogContainer {\n border: none;\n background: #fafafa;\n z-index: 999999;\n}\n\n.wrs_modal_dialogContainer.wrs_modal_desktop {\n font-size: 14px;\n display: flex;\n flex-flow: column;\n}\n\n.wrs_modal_dialogContainer.wrs_modal_desktop.wrs_maximized {\n position: fixed;\n}\n\n.wrs_modal_dialogContainer.wrs_modal_desktop.wrs_minimized {\n position: fixed;\n top: inherit;\n margin: 0;\n margin-right: 10px;\n}\n\n.wrs_modal_dialogContainer.wrs_closed {\n visibility: hidden;\n display: none;\n opacity: 0;\n}\n\n/* Class that exists but hasn't got css properties defined\n.wrs_modal_dialogContainer.wrs_modal_desktop.wrs_minimized.wrs_drag {} */\n\n.wrs_modal_dialogContainer.wrs_modal_desktop.wrs_stack {\n position: fixed;\n bottom: 0;\n right: 0;\n box-shadow: rgba(0, 0, 0, 0.5) 0 2px 8px;\n}\n\n.wrs_modal_dialogContainer.wrs_drag {\n box-shadow: rgba(0, 0, 0, 0.5) 0 2px 8px;\n}\n\n.wrs_modal_dialogContainer.wrs_modal_desktop.wrs_drag {\n box-shadow: rgba(0, 0, 0, 0.5) 0 2px 8px;\n}\n\n.wrs_modal_dialogContainer.wrs_modal_ios,\n.wrs_modal_dialogContainer.wrs_modal_android {\n margin: 0px;\n position: fixed;\n height: auto;\n overflow: hidden;\n top: calc(env(safe-area-inset-top));\n right: calc(env(safe-area-inset-right));\n left: calc(env(safe-area-inset-left));\n bottom: 0px;\n transform: none;\n box-sizing: border-box;\n display: flex;\n flex-direction: column;\n}\n\n/* Class that exists but hasn't got css properties defined\n.wrs_content_container.wrs_maximized {} */\n\n.wrs_content_container.wrs_minimized {\n display: none;\n}\n\n/* .wrs_editor {\n flex-grow: 1;\n} */\n\n.wrs_content_container.wrs_modal_desktop > div:first-child {\n flex-grow: 1;\n}\n\n.wrs_modal_wrapper.wrs_modal_android,\n.wrs_modal_wrapper.wrs_modal_ios {\n margin: 0.5rem !important;\n flex-grow: 0.98;\n display: flex;\n}\n\n.wrs_content_container.wrs_modal_ios,\n.wrs_content_container.wrs_modal_android {\n flex-grow: 0.98;\n display: flex;\n}\n\n.wrs_content_container.wrs_modal_desktop {\n width: 100%;\n flex-grow: 1;\n display: flex;\n flex-direction: column;\n height: auto !important;\n}\n\n.wrs_modal_dialogContainer.wrs_modal_badStock {\n width: 100%;\n height: 280px;\n margin: 0 auto;\n border-width: 0;\n}\n\n.wrs_modal_wrapper.wrs_modal_badStock {\n width: 100%;\n height: 280px;\n margin: 0 auto;\n border-width: 0;\n}\n\n.wrs_noselect {\n -moz-user-select: none;\n -khtml-user-select: none;\n -webkit-user-select: none;\n -ms-user-select: none;\n user-select: none;\n}\n\n.wrs_bottom_right_resizer {\n width: 10px;\n height: 10px;\n color: #778e9a;\n position: absolute;\n right: 4px;\n bottom: 8px;\n cursor: se-resize;\n -moz-user-select: none;\n -webkit-user-select: none;\n -ms-user-select: none;\n user-select: none;\n}\n\n.wrs_bottom_left_resizer {\n width: 15px;\n height: 15px;\n color: #778e9a;\n position: absolute;\n left: 0;\n top: 0;\n cursor: se-resize;\n}\n\n.wrs_modal_controls {\n height: 42px;\n line-height: normal;\n}\n\n.wrs_modal_links {\n margin: 10px auto;\n margin-bottom: 0;\n font-family: arial, sans-serif;\n padding: 6px;\n display: inline;\n float: right;\n text-align: right;\n}\n\n.wrs_modal_links > a {\n text-decoration: none;\n color: #778e9a;\n font-size: 16px;\n}\n\n.wrs_modal_button_cancel,\n.wrs_modal_button_cancel:hover,\n.wrs_modal_button_cancel:visited,\n.wrs_modal_button_cancel:active,\n.wrs_modal_button_cancel:focus {\n min-width: 80px;\n font-size: 14px;\n border-radius: 3px;\n border: 1px solid #778e9a;\n padding: 6px 8px;\n margin: 10px auto;\n margin-left: 5px;\n margin-bottom: 0;\n cursor: pointer;\n font-family: arial, sans-serif;\n background-color: #ddd;\n height: 32px;\n}\n\n.wrs_modal_button_accept,\n.wrs_modal_button_accept:hover,\n.wrs_modal_button_accept:visited,\n.wrs_modal_button_accept:active,\n.wrs_modal_button_accept:focus {\n min-width: 80px;\n font-size: 14px;\n border-radius: 3px;\n border: 1px solid #778e9a;\n padding: 6px 8px;\n margin: 10px auto;\n margin-right: 5px;\n margin-bottom: 0;\n color: #fff;\n background: #778e9a;\n cursor: pointer;\n font-family: arial, sans-serif;\n height: 32px;\n}\n\n.wrs_editor_vertical_bar {\n height: 20px;\n float: right;\n background: none;\n width: 20px;\n cursor: pointer;\n}\n\n.wrs_modal_buttons_container {\n display: inline;\n float: left;\n}\n\n.wrs_modal_buttons_container.wrs_modalDesktop {\n padding-left: 0;\n}\n\n.wrs_modal_buttons_container > button {\n line-height: normal;\n background-image: none;\n}\n\n.wrs_modal_wrapper {\n margin: 6px;\n display: flex;\n flex-direction: column;\n flex-grow: 1;\n}\n\n.wrs_modal_wrapper.wrs_modal_desktop.wrs_minimized {\n display: none;\n}\n\n.wrs_popupmessage_overlay_envolture {\n display: none;\n width: 100%;\n}\n\n.wrs_popupmessage_overlay {\n position: absolute;\n width: 100%;\n height: 100%;\n top: 0;\n left: 0;\n right: 0;\n bottom: 0;\n background-color: rgba(0, 0, 0, 0.5);\n z-index: 4;\n cursor: pointer;\n}\n\n.wrs_popupmessage_panel {\n top: 50%;\n left: 50%;\n transform: translate(-50%, -50%);\n position: absolute;\n background: white;\n max-width: 500px;\n width: 75%;\n border-radius: 2px;\n padding: 20px;\n font-family: sans-serif;\n font-size: 15px;\n text-align: left;\n color: #2e2e2e;\n z-index: 5;\n max-height: 75%;\n overflow: auto;\n}\n\n.wrs_popupmessage_button_area {\n margin: 10px 0 0 0;\n}\n\n.wrs_panelContainer * {\n border: 0;\n}\n\n.wrs_button_cancel,\n.wrs_button_cancel:hover,\n.wrs_button_cancel:visited,\n.wrs_button_cancel:active,\n.wrs_button_cancel:focus {\n min-width: 80px;\n font-size: 14px;\n border-radius: 3px;\n border: 1px solid #778e9a;\n padding: 6px 8px;\n margin: 10px auto;\n margin-left: 5px;\n margin-bottom: 0;\n cursor: pointer;\n font-family: arial, sans-serif;\n background-color: #ddd;\n background-image: none;\n height: 32px;\n}\n\n.wrs_button_accept,\n.wrs_button_accept:hover,\n.wrs_button_accept:visited,\n.wrs_button_accept:active,\n.wrs_button_accept:focus {\n min-width: 80px;\n font-size: 14px;\n border-radius: 3px;\n border: 1px solid #778e9a;\n padding: 6px 8px;\n margin: 10px auto;\n margin-right: 5px;\n margin-bottom: 0;\n color: #fff;\n background: #778e9a;\n cursor: pointer;\n font-family: arial, sans-serif;\n height: 32px;\n}\n\n.wrs_editor button {\n box-shadow: none;\n}\n\n.wrs_editor .wrs_header button {\n border-bottom: none;\n border-bottom-left-radius: 0;\n border-bottom-right-radius: 0;\n}\n\n.wrs_modal_overlay.wrs_modal_desktop.wrs_stack.wrs_overlay_active {\n display: block;\n}\n\n/* Fix selection in drupal style */\n.wrs_toolbar tr:focus {\n background: none;\n}\n\n.wrs_toolbar tr:hover {\n background: none;\n}\n\n/* End of fix drupal */\n.wrs_modal_rtl .wrs_modal_button_cancel {\n margin-right: 5px;\n margin-left: 0;\n}\n\n.wrs_modal_rtl .wrs_modal_button_accept {\n margin-right: 0;\n margin-left: 5px;\n}\n\n.wrs_modal_rtl .wrs_button_cancel {\n margin-right: 5px;\n margin-left: 0;\n}\n\n.wrs_modal_rtl .wrs_button_accept {\n margin-right: 0;\n margin-left: 5px;\n}\n\n/* The Offline Modal (background) */\n.wrs_modal_offline {\n display: none;\n /* Hidden by default */\n position: fixed;\n /* Stay in place */\n z-index: 2;\n /* Sit on top */\n padding-top: 150px;\n /* Location of the box */\n left: 0;\n top: 0;\n width: 100%;\n /* Full width */\n height: 100%;\n /* Full height */\n overflow: auto;\n /* Enable scroll if needed */\n background-color: rgb(0, 0, 0);\n /* Fallback color */\n background-color: rgba(0, 0, 0, 0.4);\n /* Black w/ opacity */\n margin: auto;\n}\n\n/* Modal Content */\n.wrs_modal_content_offline {\n margin: auto;\n padding: 16px;\n background: #fff7ed;\n border-radius: 6px;\n width: 517px;\n height: 100px;\n position: relative;\n}\n\n/* The Close Button */\n.wrs_modal_offline_close {\n color: #c2410c;\n font-size: 24px;\n font-weight: bold;\n left: 95.7%;\n right: 2.08%;\n top: 7.6%;\n bottom: 75%;\n font-family: \"Inter\";\n font-style: normal;\n font-weight: 400;\n position: absolute;\n}\n\n/* The Warn Icon */\n.wrs_modal_offline_warn {\n position: absolute;\n left: 2.08%;\n right: 94%;\n top: 11.6%;\n bottom: 75%;\n font-size: 24px;\n color: #fb923c;\n background-repeat: no-repeat;\n}\n\n.wrs_modal_offline_text_container {\n position: absolute;\n left: 6.8%;\n right: 6.08%;\n top: 10.4%;\n bottom: 2%;\n}\n\n.wrs_modal_offline_text {\n font-family: \"Inter\";\n font-style: normal;\n font-weight: 400;\n font-size: 14px;\n line-height: 20px;\n color: #c2410c;\n}\n\n.wrs_modal_offline_text_warn {\n height: 25px;\n font-family: \"Inter\";\n font-style: normal;\n font-size: 14px;\n line-height: 20px;\n font-weight: bold;\n color: #c2410c;\n}\n\n.wrs_modal_offline_close:hover,\n.wrs_modal_offline_close:focus {\n color: #000;\n text-decoration: none;\n cursor: pointer;\n}\n","/* Replace the default suggestion marker border because it's doesn't span the entire widget. */\n.ck-widget.ck-math-widget.ck-suggestion-marker {\n border: none;\n\n &.ck-suggestion-marker-insertion {\n & .Wirisformula {\n border: 3px solid var(--ck-color-suggestion-marker-insertion-border);\n }\n\n &.ck-suggestion-marker--active {\n & .Wirisformula {\n border: 3px solid var(--ck-color-suggestion-marker-insertion-border-active);\n }\n }\n }\n\n &.ck-suggestion-marker-deletion {\n & .Wirisformula {\n border: 3px solid var(--ck-color-suggestion-marker-deletion-border);\n }\n\n &.ck-suggestion-marker--active {\n & .Wirisformula {\n border: 3px solid var(--ck-color-suggestion-marker-deletion-border-active);\n }\n }\n }\n}\n"]}
\ No newline at end of file
diff --git a/packages/ckeditor5/dist/browser/index.js b/packages/ckeditor5/dist/browser/index.js
index c2383bfed..25ce83d20 100644
--- a/packages/ckeditor5/dist/browser/index.js
+++ b/packages/ckeditor5/dist/browser/index.js
@@ -3,7 +3,7 @@ import { ButtonView } from 'ckeditor5';
import { ClickObserver, XmlDataProcessor, ViewUpcastWriter, HtmlDataProcessor } from 'ckeditor5';
import { Widget, viewToModelPositionOutsideModelElement, toWidget } from 'ckeditor5';
-/*! @license DOMPurify 3.3.1 | (c) Cure53 and other contributors | Released under the Apache license 2.0 and Mozilla Public License 2.0 | github.com/cure53/DOMPurify/blob/3.3.1/LICENSE */
+/*! @license DOMPurify 3.3.3 | (c) Cure53 and other contributors | Released under the Apache license 2.0 and Mozilla Public License 2.0 | github.com/cure53/DOMPurify/blob/3.3.3/LICENSE */
const {
entries,
@@ -301,7 +301,7 @@ const _createHooksMap = function _createHooksMap() {
function createDOMPurify() {
let window = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : getGlobal();
const DOMPurify = root => createDOMPurify(root);
- DOMPurify.version = '3.3.1';
+ DOMPurify.version = '3.3.3';
DOMPurify.removed = [];
if (!window || !window.document || window.document.nodeType !== NODE_TYPE.document || !window.Element) {
// Not running in a browser, provide a factory function
@@ -596,7 +596,7 @@ function createDOMPurify() {
/* Parse profile info */
if (USE_PROFILES) {
ALLOWED_TAGS = addToSet({}, text);
- ALLOWED_ATTR = [];
+ ALLOWED_ATTR = create(null);
if (USE_PROFILES.html === true) {
addToSet(ALLOWED_TAGS, html$1);
addToSet(ALLOWED_ATTR, html);
@@ -617,6 +617,13 @@ function createDOMPurify() {
addToSet(ALLOWED_ATTR, xml);
}
}
+ /* Prevent function-based ADD_ATTR / ADD_TAGS from leaking across calls */
+ if (!objectHasOwnProperty(cfg, 'ADD_TAGS')) {
+ EXTRA_ELEMENT_HANDLING.tagCheck = null;
+ }
+ if (!objectHasOwnProperty(cfg, 'ADD_ATTR')) {
+ EXTRA_ELEMENT_HANDLING.attributeCheck = null;
+ }
/* Merge configuration parameters */
if (cfg.ADD_TAGS) {
if (typeof cfg.ADD_TAGS === 'function') {
@@ -1014,6 +1021,10 @@ function createDOMPurify() {
*/
// eslint-disable-next-line complexity
const _isValidAttribute = function _isValidAttribute(lcTag, lcName, value) {
+ /* FORBID_ATTR must always win, even if ADD_ATTR predicate would allow it */
+ if (FORBID_ATTR[lcName]) {
+ return false;
+ }
/* Make sure attribute cannot clobber */
if (SANITIZE_DOM && (lcName === 'id' || lcName === 'name') && (value in document || value in formElement)) {
return false;
@@ -1106,7 +1117,7 @@ function createDOMPurify() {
value = SANITIZE_NAMED_PROPS_PREFIX + value;
}
/* Work around a security issue with comments inside attributes */
- if (SAFE_FOR_XML && regExpTest(/((--!?|])>)|<\/(style|title|textarea)/i, value)) {
+ if (SAFE_FOR_XML && regExpTest(/((--!?|])>)|<\/(style|script|title|xmp|textarea|noscript|iframe|noembed|noframes)/i, value)) {
_removeAttribute(name, currentNode);
continue;
}
@@ -11608,13 +11619,14 @@ delete Array.prototype.__class__; // @codingStandardsIgnoreEnd
const standardResult = Latex.getLatexFromTextNode(textNode, caretPosition);
const acceptedLatex = this.extractAcceptedLatexFromDOM(textNode, caretPosition);
// Prioritize accepted LaTeX if it differs from standard extraction (for track changes compatibility).
- // Use explicit undefined check to allow empty LaTeX strings.
+ // Important node: use explicit undefined check to allow empty LaTeX strings, otherwise it would not detect $$$$ as valid LaTeX.
const latex = acceptedLatex !== undefined && acceptedLatex !== standardResult?.latex ? acceptedLatex : standardResult?.latex;
+ // Do not continue if no LaTeX found by either method.
+ // This is necessary since both parameters can be independently undefined in some edge cases.
if (latex === undefined && acceptedLatex === undefined) {
return;
}
// Verify caret is inside LaTeX block for track changes edge cases.
- // Use explicit undefined check to allow empty LaTeX strings.
if (!standardResult && acceptedLatex !== undefined && !this.isCaretInsideLatexBlock(textNode, caretPosition)) {
return;
}
@@ -11624,7 +11636,6 @@ delete Array.prototype.__class__; // @codingStandardsIgnoreEnd
}
isCaretInsideLatexBlock(textNode, caretPosition = 0) {
// If LaTeX is found, the caret is inside one.
- // Use explicit undefined check to allow empty LaTeX strings.
return this.extractAcceptedLatexFromDOM(textNode, caretPosition) !== undefined;
}
/**
@@ -11793,7 +11804,7 @@ var mathIcon = "\n\n\n";
-var version = "8.15.1";
+var version = "8.15.2";
var packageInfo = {
version: version};
diff --git a/packages/ckeditor5/dist/browser/index.js.map b/packages/ckeditor5/dist/browser/index.js.map
index 18f0b5961..40cb2e792 100644
--- a/packages/ckeditor5/dist/browser/index.js.map
+++ b/packages/ckeditor5/dist/browser/index.js.map
@@ -1 +1 @@
-{"version":3,"file":"index.js","sources":["../../../../node_modules/dompurify/dist/purify.es.mjs","../../../devkit/src/constants.js","../../../devkit/src/mathml.js","../../../devkit/src/configuration.js","../../../devkit/src/textcache.js","../../../devkit/src/listeners.js","../../../devkit/src/serviceprovider.js","../../../devkit/src/latex.js","../../../devkit/src/stringmanager.js","../../../devkit/src/util.js","../../../devkit/src/image.js","../../../devkit/src/accessibility.js","../../../devkit/src/parser.js","../../../devkit/src/editorlistener.js","../../../devkit/telemeter-wasm/telemeter_wasm.js","../../../devkit/src/telemeter.js","../../../devkit/src/contentmanager.js","../../../devkit/src/customeditors.js","../../../devkit/src/jsvariables.js","../../../devkit/src/event.js","../../../devkit/src/popupmessage.js","../../../devkit/src/focusprotection.js","../../../devkit/src/modal.js","../../../devkit/src/polyfills.js","../../../devkit/src/core.src.js","../../../devkit/src/integrationmodel.js","../../../devkit/src/md5.js","../../src/integration.js","../../src/commands.js","../../src/plugin.js"],"sourcesContent":["/*! @license DOMPurify 3.3.1 | (c) Cure53 and other contributors | Released under the Apache license 2.0 and Mozilla Public License 2.0 | github.com/cure53/DOMPurify/blob/3.3.1/LICENSE */\n\nconst {\n entries,\n setPrototypeOf,\n isFrozen,\n getPrototypeOf,\n getOwnPropertyDescriptor\n} = Object;\nlet {\n freeze,\n seal,\n create\n} = Object; // eslint-disable-line import/no-mutable-exports\nlet {\n apply,\n construct\n} = typeof Reflect !== 'undefined' && Reflect;\nif (!freeze) {\n freeze = function freeze(x) {\n return x;\n };\n}\nif (!seal) {\n seal = function seal(x) {\n return x;\n };\n}\nif (!apply) {\n apply = function apply(func, thisArg) {\n for (var _len = arguments.length, args = new Array(_len > 2 ? _len - 2 : 0), _key = 2; _key < _len; _key++) {\n args[_key - 2] = arguments[_key];\n }\n return func.apply(thisArg, args);\n };\n}\nif (!construct) {\n construct = function construct(Func) {\n for (var _len2 = arguments.length, args = new Array(_len2 > 1 ? _len2 - 1 : 0), _key2 = 1; _key2 < _len2; _key2++) {\n args[_key2 - 1] = arguments[_key2];\n }\n return new Func(...args);\n };\n}\nconst arrayForEach = unapply(Array.prototype.forEach);\nconst arrayLastIndexOf = unapply(Array.prototype.lastIndexOf);\nconst arrayPop = unapply(Array.prototype.pop);\nconst arrayPush = unapply(Array.prototype.push);\nconst arraySplice = unapply(Array.prototype.splice);\nconst stringToLowerCase = unapply(String.prototype.toLowerCase);\nconst stringToString = unapply(String.prototype.toString);\nconst stringMatch = unapply(String.prototype.match);\nconst stringReplace = unapply(String.prototype.replace);\nconst stringIndexOf = unapply(String.prototype.indexOf);\nconst stringTrim = unapply(String.prototype.trim);\nconst objectHasOwnProperty = unapply(Object.prototype.hasOwnProperty);\nconst regExpTest = unapply(RegExp.prototype.test);\nconst typeErrorCreate = unconstruct(TypeError);\n/**\n * Creates a new function that calls the given function with a specified thisArg and arguments.\n *\n * @param func - The function to be wrapped and called.\n * @returns A new function that calls the given function with a specified thisArg and arguments.\n */\nfunction unapply(func) {\n return function (thisArg) {\n if (thisArg instanceof RegExp) {\n thisArg.lastIndex = 0;\n }\n for (var _len3 = arguments.length, args = new Array(_len3 > 1 ? _len3 - 1 : 0), _key3 = 1; _key3 < _len3; _key3++) {\n args[_key3 - 1] = arguments[_key3];\n }\n return apply(func, thisArg, args);\n };\n}\n/**\n * Creates a new function that constructs an instance of the given constructor function with the provided arguments.\n *\n * @param func - The constructor function to be wrapped and called.\n * @returns A new function that constructs an instance of the given constructor function with the provided arguments.\n */\nfunction unconstruct(Func) {\n return function () {\n for (var _len4 = arguments.length, args = new Array(_len4), _key4 = 0; _key4 < _len4; _key4++) {\n args[_key4] = arguments[_key4];\n }\n return construct(Func, args);\n };\n}\n/**\n * Add properties to a lookup table\n *\n * @param set - The set to which elements will be added.\n * @param array - The array containing elements to be added to the set.\n * @param transformCaseFunc - An optional function to transform the case of each element before adding to the set.\n * @returns The modified set with added elements.\n */\nfunction addToSet(set, array) {\n let transformCaseFunc = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : stringToLowerCase;\n if (setPrototypeOf) {\n // Make 'in' and truthy checks like Boolean(set.constructor)\n // independent of any properties defined on Object.prototype.\n // Prevent prototype setters from intercepting set as a this value.\n setPrototypeOf(set, null);\n }\n let l = array.length;\n while (l--) {\n let element = array[l];\n if (typeof element === 'string') {\n const lcElement = transformCaseFunc(element);\n if (lcElement !== element) {\n // Config presets (e.g. tags.js, attrs.js) are immutable.\n if (!isFrozen(array)) {\n array[l] = lcElement;\n }\n element = lcElement;\n }\n }\n set[element] = true;\n }\n return set;\n}\n/**\n * Clean up an array to harden against CSPP\n *\n * @param array - The array to be cleaned.\n * @returns The cleaned version of the array\n */\nfunction cleanArray(array) {\n for (let index = 0; index < array.length; index++) {\n const isPropertyExist = objectHasOwnProperty(array, index);\n if (!isPropertyExist) {\n array[index] = null;\n }\n }\n return array;\n}\n/**\n * Shallow clone an object\n *\n * @param object - The object to be cloned.\n * @returns A new object that copies the original.\n */\nfunction clone(object) {\n const newObject = create(null);\n for (const [property, value] of entries(object)) {\n const isPropertyExist = objectHasOwnProperty(object, property);\n if (isPropertyExist) {\n if (Array.isArray(value)) {\n newObject[property] = cleanArray(value);\n } else if (value && typeof value === 'object' && value.constructor === Object) {\n newObject[property] = clone(value);\n } else {\n newObject[property] = value;\n }\n }\n }\n return newObject;\n}\n/**\n * This method automatically checks if the prop is function or getter and behaves accordingly.\n *\n * @param object - The object to look up the getter function in its prototype chain.\n * @param prop - The property name for which to find the getter function.\n * @returns The getter function found in the prototype chain or a fallback function.\n */\nfunction lookupGetter(object, prop) {\n while (object !== null) {\n const desc = getOwnPropertyDescriptor(object, prop);\n if (desc) {\n if (desc.get) {\n return unapply(desc.get);\n }\n if (typeof desc.value === 'function') {\n return unapply(desc.value);\n }\n }\n object = getPrototypeOf(object);\n }\n function fallbackValue() {\n return null;\n }\n return fallbackValue;\n}\n\nconst html$1 = freeze(['a', 'abbr', 'acronym', 'address', 'area', 'article', 'aside', 'audio', 'b', 'bdi', 'bdo', 'big', 'blink', 'blockquote', 'body', 'br', 'button', 'canvas', 'caption', 'center', 'cite', 'code', 'col', 'colgroup', 'content', 'data', 'datalist', 'dd', 'decorator', 'del', 'details', 'dfn', 'dialog', 'dir', 'div', 'dl', 'dt', 'element', 'em', 'fieldset', 'figcaption', 'figure', 'font', 'footer', 'form', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'head', 'header', 'hgroup', 'hr', 'html', 'i', 'img', 'input', 'ins', 'kbd', 'label', 'legend', 'li', 'main', 'map', 'mark', 'marquee', 'menu', 'menuitem', 'meter', 'nav', 'nobr', 'ol', 'optgroup', 'option', 'output', 'p', 'picture', 'pre', 'progress', 'q', 'rp', 'rt', 'ruby', 's', 'samp', 'search', 'section', 'select', 'shadow', 'slot', 'small', 'source', 'spacer', 'span', 'strike', 'strong', 'style', 'sub', 'summary', 'sup', 'table', 'tbody', 'td', 'template', 'textarea', 'tfoot', 'th', 'thead', 'time', 'tr', 'track', 'tt', 'u', 'ul', 'var', 'video', 'wbr']);\nconst svg$1 = freeze(['svg', 'a', 'altglyph', 'altglyphdef', 'altglyphitem', 'animatecolor', 'animatemotion', 'animatetransform', 'circle', 'clippath', 'defs', 'desc', 'ellipse', 'enterkeyhint', 'exportparts', 'filter', 'font', 'g', 'glyph', 'glyphref', 'hkern', 'image', 'inputmode', 'line', 'lineargradient', 'marker', 'mask', 'metadata', 'mpath', 'part', 'path', 'pattern', 'polygon', 'polyline', 'radialgradient', 'rect', 'stop', 'style', 'switch', 'symbol', 'text', 'textpath', 'title', 'tref', 'tspan', 'view', 'vkern']);\nconst svgFilters = freeze(['feBlend', 'feColorMatrix', 'feComponentTransfer', 'feComposite', 'feConvolveMatrix', 'feDiffuseLighting', 'feDisplacementMap', 'feDistantLight', 'feDropShadow', 'feFlood', 'feFuncA', 'feFuncB', 'feFuncG', 'feFuncR', 'feGaussianBlur', 'feImage', 'feMerge', 'feMergeNode', 'feMorphology', 'feOffset', 'fePointLight', 'feSpecularLighting', 'feSpotLight', 'feTile', 'feTurbulence']);\n// List of SVG elements that are disallowed by default.\n// We still need to know them so that we can do namespace\n// checks properly in case one wants to add them to\n// allow-list.\nconst svgDisallowed = freeze(['animate', 'color-profile', 'cursor', 'discard', 'font-face', 'font-face-format', 'font-face-name', 'font-face-src', 'font-face-uri', 'foreignobject', 'hatch', 'hatchpath', 'mesh', 'meshgradient', 'meshpatch', 'meshrow', 'missing-glyph', 'script', 'set', 'solidcolor', 'unknown', 'use']);\nconst mathMl$1 = freeze(['math', 'menclose', 'merror', 'mfenced', 'mfrac', 'mglyph', 'mi', 'mlabeledtr', 'mmultiscripts', 'mn', 'mo', 'mover', 'mpadded', 'mphantom', 'mroot', 'mrow', 'ms', 'mspace', 'msqrt', 'mstyle', 'msub', 'msup', 'msubsup', 'mtable', 'mtd', 'mtext', 'mtr', 'munder', 'munderover', 'mprescripts']);\n// Similarly to SVG, we want to know all MathML elements,\n// even those that we disallow by default.\nconst mathMlDisallowed = freeze(['maction', 'maligngroup', 'malignmark', 'mlongdiv', 'mscarries', 'mscarry', 'msgroup', 'mstack', 'msline', 'msrow', 'semantics', 'annotation', 'annotation-xml', 'mprescripts', 'none']);\nconst text = freeze(['#text']);\n\nconst html = freeze(['accept', 'action', 'align', 'alt', 'autocapitalize', 'autocomplete', 'autopictureinpicture', 'autoplay', 'background', 'bgcolor', 'border', 'capture', 'cellpadding', 'cellspacing', 'checked', 'cite', 'class', 'clear', 'color', 'cols', 'colspan', 'controls', 'controlslist', 'coords', 'crossorigin', 'datetime', 'decoding', 'default', 'dir', 'disabled', 'disablepictureinpicture', 'disableremoteplayback', 'download', 'draggable', 'enctype', 'enterkeyhint', 'exportparts', 'face', 'for', 'headers', 'height', 'hidden', 'high', 'href', 'hreflang', 'id', 'inert', 'inputmode', 'integrity', 'ismap', 'kind', 'label', 'lang', 'list', 'loading', 'loop', 'low', 'max', 'maxlength', 'media', 'method', 'min', 'minlength', 'multiple', 'muted', 'name', 'nonce', 'noshade', 'novalidate', 'nowrap', 'open', 'optimum', 'part', 'pattern', 'placeholder', 'playsinline', 'popover', 'popovertarget', 'popovertargetaction', 'poster', 'preload', 'pubdate', 'radiogroup', 'readonly', 'rel', 'required', 'rev', 'reversed', 'role', 'rows', 'rowspan', 'spellcheck', 'scope', 'selected', 'shape', 'size', 'sizes', 'slot', 'span', 'srclang', 'start', 'src', 'srcset', 'step', 'style', 'summary', 'tabindex', 'title', 'translate', 'type', 'usemap', 'valign', 'value', 'width', 'wrap', 'xmlns', 'slot']);\nconst svg = freeze(['accent-height', 'accumulate', 'additive', 'alignment-baseline', 'amplitude', 'ascent', 'attributename', 'attributetype', 'azimuth', 'basefrequency', 'baseline-shift', 'begin', 'bias', 'by', 'class', 'clip', 'clippathunits', 'clip-path', 'clip-rule', 'color', 'color-interpolation', 'color-interpolation-filters', 'color-profile', 'color-rendering', 'cx', 'cy', 'd', 'dx', 'dy', 'diffuseconstant', 'direction', 'display', 'divisor', 'dur', 'edgemode', 'elevation', 'end', 'exponent', 'fill', 'fill-opacity', 'fill-rule', 'filter', 'filterunits', 'flood-color', 'flood-opacity', 'font-family', 'font-size', 'font-size-adjust', 'font-stretch', 'font-style', 'font-variant', 'font-weight', 'fx', 'fy', 'g1', 'g2', 'glyph-name', 'glyphref', 'gradientunits', 'gradienttransform', 'height', 'href', 'id', 'image-rendering', 'in', 'in2', 'intercept', 'k', 'k1', 'k2', 'k3', 'k4', 'kerning', 'keypoints', 'keysplines', 'keytimes', 'lang', 'lengthadjust', 'letter-spacing', 'kernelmatrix', 'kernelunitlength', 'lighting-color', 'local', 'marker-end', 'marker-mid', 'marker-start', 'markerheight', 'markerunits', 'markerwidth', 'maskcontentunits', 'maskunits', 'max', 'mask', 'mask-type', 'media', 'method', 'mode', 'min', 'name', 'numoctaves', 'offset', 'operator', 'opacity', 'order', 'orient', 'orientation', 'origin', 'overflow', 'paint-order', 'path', 'pathlength', 'patterncontentunits', 'patterntransform', 'patternunits', 'points', 'preservealpha', 'preserveaspectratio', 'primitiveunits', 'r', 'rx', 'ry', 'radius', 'refx', 'refy', 'repeatcount', 'repeatdur', 'restart', 'result', 'rotate', 'scale', 'seed', 'shape-rendering', 'slope', 'specularconstant', 'specularexponent', 'spreadmethod', 'startoffset', 'stddeviation', 'stitchtiles', 'stop-color', 'stop-opacity', 'stroke-dasharray', 'stroke-dashoffset', 'stroke-linecap', 'stroke-linejoin', 'stroke-miterlimit', 'stroke-opacity', 'stroke', 'stroke-width', 'style', 'surfacescale', 'systemlanguage', 'tabindex', 'tablevalues', 'targetx', 'targety', 'transform', 'transform-origin', 'text-anchor', 'text-decoration', 'text-rendering', 'textlength', 'type', 'u1', 'u2', 'unicode', 'values', 'viewbox', 'visibility', 'version', 'vert-adv-y', 'vert-origin-x', 'vert-origin-y', 'width', 'word-spacing', 'wrap', 'writing-mode', 'xchannelselector', 'ychannelselector', 'x', 'x1', 'x2', 'xmlns', 'y', 'y1', 'y2', 'z', 'zoomandpan']);\nconst mathMl = freeze(['accent', 'accentunder', 'align', 'bevelled', 'close', 'columnsalign', 'columnlines', 'columnspan', 'denomalign', 'depth', 'dir', 'display', 'displaystyle', 'encoding', 'fence', 'frame', 'height', 'href', 'id', 'largeop', 'length', 'linethickness', 'lspace', 'lquote', 'mathbackground', 'mathcolor', 'mathsize', 'mathvariant', 'maxsize', 'minsize', 'movablelimits', 'notation', 'numalign', 'open', 'rowalign', 'rowlines', 'rowspacing', 'rowspan', 'rspace', 'rquote', 'scriptlevel', 'scriptminsize', 'scriptsizemultiplier', 'selection', 'separator', 'separators', 'stretchy', 'subscriptshift', 'supscriptshift', 'symmetric', 'voffset', 'width', 'xmlns']);\nconst xml = freeze(['xlink:href', 'xml:id', 'xlink:title', 'xml:space', 'xmlns:xlink']);\n\n// eslint-disable-next-line unicorn/better-regex\nconst MUSTACHE_EXPR = seal(/\\{\\{[\\w\\W]*|[\\w\\W]*\\}\\}/gm); // Specify template detection regex for SAFE_FOR_TEMPLATES mode\nconst ERB_EXPR = seal(/<%[\\w\\W]*|[\\w\\W]*%>/gm);\nconst TMPLIT_EXPR = seal(/\\$\\{[\\w\\W]*/gm); // eslint-disable-line unicorn/better-regex\nconst DATA_ATTR = seal(/^data-[\\-\\w.\\u00B7-\\uFFFF]+$/); // eslint-disable-line no-useless-escape\nconst ARIA_ATTR = seal(/^aria-[\\-\\w]+$/); // eslint-disable-line no-useless-escape\nconst IS_ALLOWED_URI = seal(/^(?:(?:(?:f|ht)tps?|mailto|tel|callto|sms|cid|xmpp|matrix):|[^a-z]|[a-z+.\\-]+(?:[^a-z+.\\-:]|$))/i // eslint-disable-line no-useless-escape\n);\nconst IS_SCRIPT_OR_DATA = seal(/^(?:\\w+script|data):/i);\nconst ATTR_WHITESPACE = seal(/[\\u0000-\\u0020\\u00A0\\u1680\\u180E\\u2000-\\u2029\\u205F\\u3000]/g // eslint-disable-line no-control-regex\n);\nconst DOCTYPE_NAME = seal(/^html$/i);\nconst CUSTOM_ELEMENT = seal(/^[a-z][.\\w]*(-[.\\w]+)+$/i);\n\nvar EXPRESSIONS = /*#__PURE__*/Object.freeze({\n __proto__: null,\n ARIA_ATTR: ARIA_ATTR,\n ATTR_WHITESPACE: ATTR_WHITESPACE,\n CUSTOM_ELEMENT: CUSTOM_ELEMENT,\n DATA_ATTR: DATA_ATTR,\n DOCTYPE_NAME: DOCTYPE_NAME,\n ERB_EXPR: ERB_EXPR,\n IS_ALLOWED_URI: IS_ALLOWED_URI,\n IS_SCRIPT_OR_DATA: IS_SCRIPT_OR_DATA,\n MUSTACHE_EXPR: MUSTACHE_EXPR,\n TMPLIT_EXPR: TMPLIT_EXPR\n});\n\n/* eslint-disable @typescript-eslint/indent */\n// https://developer.mozilla.org/en-US/docs/Web/API/Node/nodeType\nconst NODE_TYPE = {\n element: 1,\n attribute: 2,\n text: 3,\n cdataSection: 4,\n entityReference: 5,\n // Deprecated\n entityNode: 6,\n // Deprecated\n progressingInstruction: 7,\n comment: 8,\n document: 9,\n documentType: 10,\n documentFragment: 11,\n notation: 12 // Deprecated\n};\nconst getGlobal = function getGlobal() {\n return typeof window === 'undefined' ? null : window;\n};\n/**\n * Creates a no-op policy for internal use only.\n * Don't export this function outside this module!\n * @param trustedTypes The policy factory.\n * @param purifyHostElement The Script element used to load DOMPurify (to determine policy name suffix).\n * @return The policy created (or null, if Trusted Types\n * are not supported or creating the policy failed).\n */\nconst _createTrustedTypesPolicy = function _createTrustedTypesPolicy(trustedTypes, purifyHostElement) {\n if (typeof trustedTypes !== 'object' || typeof trustedTypes.createPolicy !== 'function') {\n return null;\n }\n // Allow the callers to control the unique policy name\n // by adding a data-tt-policy-suffix to the script element with the DOMPurify.\n // Policy creation with duplicate names throws in Trusted Types.\n let suffix = null;\n const ATTR_NAME = 'data-tt-policy-suffix';\n if (purifyHostElement && purifyHostElement.hasAttribute(ATTR_NAME)) {\n suffix = purifyHostElement.getAttribute(ATTR_NAME);\n }\n const policyName = 'dompurify' + (suffix ? '#' + suffix : '');\n try {\n return trustedTypes.createPolicy(policyName, {\n createHTML(html) {\n return html;\n },\n createScriptURL(scriptUrl) {\n return scriptUrl;\n }\n });\n } catch (_) {\n // Policy creation failed (most likely another DOMPurify script has\n // already run). Skip creating the policy, as this will only cause errors\n // if TT are enforced.\n console.warn('TrustedTypes policy ' + policyName + ' could not be created.');\n return null;\n }\n};\nconst _createHooksMap = function _createHooksMap() {\n return {\n afterSanitizeAttributes: [],\n afterSanitizeElements: [],\n afterSanitizeShadowDOM: [],\n beforeSanitizeAttributes: [],\n beforeSanitizeElements: [],\n beforeSanitizeShadowDOM: [],\n uponSanitizeAttribute: [],\n uponSanitizeElement: [],\n uponSanitizeShadowNode: []\n };\n};\nfunction createDOMPurify() {\n let window = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : getGlobal();\n const DOMPurify = root => createDOMPurify(root);\n DOMPurify.version = '3.3.1';\n DOMPurify.removed = [];\n if (!window || !window.document || window.document.nodeType !== NODE_TYPE.document || !window.Element) {\n // Not running in a browser, provide a factory function\n // so that you can pass your own Window\n DOMPurify.isSupported = false;\n return DOMPurify;\n }\n let {\n document\n } = window;\n const originalDocument = document;\n const currentScript = originalDocument.currentScript;\n const {\n DocumentFragment,\n HTMLTemplateElement,\n Node,\n Element,\n NodeFilter,\n NamedNodeMap = window.NamedNodeMap || window.MozNamedAttrMap,\n HTMLFormElement,\n DOMParser,\n trustedTypes\n } = window;\n const ElementPrototype = Element.prototype;\n const cloneNode = lookupGetter(ElementPrototype, 'cloneNode');\n const remove = lookupGetter(ElementPrototype, 'remove');\n const getNextSibling = lookupGetter(ElementPrototype, 'nextSibling');\n const getChildNodes = lookupGetter(ElementPrototype, 'childNodes');\n const getParentNode = lookupGetter(ElementPrototype, 'parentNode');\n // As per issue #47, the web-components registry is inherited by a\n // new document created via createHTMLDocument. As per the spec\n // (http://w3c.github.io/webcomponents/spec/custom/#creating-and-passing-registries)\n // a new empty registry is used when creating a template contents owner\n // document, so we use that as our parent document to ensure nothing\n // is inherited.\n if (typeof HTMLTemplateElement === 'function') {\n const template = document.createElement('template');\n if (template.content && template.content.ownerDocument) {\n document = template.content.ownerDocument;\n }\n }\n let trustedTypesPolicy;\n let emptyHTML = '';\n const {\n implementation,\n createNodeIterator,\n createDocumentFragment,\n getElementsByTagName\n } = document;\n const {\n importNode\n } = originalDocument;\n let hooks = _createHooksMap();\n /**\n * Expose whether this browser supports running the full DOMPurify.\n */\n DOMPurify.isSupported = typeof entries === 'function' && typeof getParentNode === 'function' && implementation && implementation.createHTMLDocument !== undefined;\n const {\n MUSTACHE_EXPR,\n ERB_EXPR,\n TMPLIT_EXPR,\n DATA_ATTR,\n ARIA_ATTR,\n IS_SCRIPT_OR_DATA,\n ATTR_WHITESPACE,\n CUSTOM_ELEMENT\n } = EXPRESSIONS;\n let {\n IS_ALLOWED_URI: IS_ALLOWED_URI$1\n } = EXPRESSIONS;\n /**\n * We consider the elements and attributes below to be safe. Ideally\n * don't add any new ones but feel free to remove unwanted ones.\n */\n /* allowed element names */\n let ALLOWED_TAGS = null;\n const DEFAULT_ALLOWED_TAGS = addToSet({}, [...html$1, ...svg$1, ...svgFilters, ...mathMl$1, ...text]);\n /* Allowed attribute names */\n let ALLOWED_ATTR = null;\n const DEFAULT_ALLOWED_ATTR = addToSet({}, [...html, ...svg, ...mathMl, ...xml]);\n /*\n * Configure how DOMPurify should handle custom elements and their attributes as well as customized built-in elements.\n * @property {RegExp|Function|null} tagNameCheck one of [null, regexPattern, predicate]. Default: `null` (disallow any custom elements)\n * @property {RegExp|Function|null} attributeNameCheck one of [null, regexPattern, predicate]. Default: `null` (disallow any attributes not on the allow list)\n * @property {boolean} allowCustomizedBuiltInElements allow custom elements derived from built-ins if they pass CUSTOM_ELEMENT_HANDLING.tagNameCheck. Default: `false`.\n */\n let CUSTOM_ELEMENT_HANDLING = Object.seal(create(null, {\n tagNameCheck: {\n writable: true,\n configurable: false,\n enumerable: true,\n value: null\n },\n attributeNameCheck: {\n writable: true,\n configurable: false,\n enumerable: true,\n value: null\n },\n allowCustomizedBuiltInElements: {\n writable: true,\n configurable: false,\n enumerable: true,\n value: false\n }\n }));\n /* Explicitly forbidden tags (overrides ALLOWED_TAGS/ADD_TAGS) */\n let FORBID_TAGS = null;\n /* Explicitly forbidden attributes (overrides ALLOWED_ATTR/ADD_ATTR) */\n let FORBID_ATTR = null;\n /* Config object to store ADD_TAGS/ADD_ATTR functions (when used as functions) */\n const EXTRA_ELEMENT_HANDLING = Object.seal(create(null, {\n tagCheck: {\n writable: true,\n configurable: false,\n enumerable: true,\n value: null\n },\n attributeCheck: {\n writable: true,\n configurable: false,\n enumerable: true,\n value: null\n }\n }));\n /* Decide if ARIA attributes are okay */\n let ALLOW_ARIA_ATTR = true;\n /* Decide if custom data attributes are okay */\n let ALLOW_DATA_ATTR = true;\n /* Decide if unknown protocols are okay */\n let ALLOW_UNKNOWN_PROTOCOLS = false;\n /* Decide if self-closing tags in attributes are allowed.\n * Usually removed due to a mXSS issue in jQuery 3.0 */\n let ALLOW_SELF_CLOSE_IN_ATTR = true;\n /* Output should be safe for common template engines.\n * This means, DOMPurify removes data attributes, mustaches and ERB\n */\n let SAFE_FOR_TEMPLATES = false;\n /* Output should be safe even for XML used within HTML and alike.\n * This means, DOMPurify removes comments when containing risky content.\n */\n let SAFE_FOR_XML = true;\n /* Decide if document with ... should be returned */\n let WHOLE_DOCUMENT = false;\n /* Track whether config is already set on this instance of DOMPurify. */\n let SET_CONFIG = false;\n /* Decide if all elements (e.g. style, script) must be children of\n * document.body. By default, browsers might move them to document.head */\n let FORCE_BODY = false;\n /* Decide if a DOM `HTMLBodyElement` should be returned, instead of a html\n * string (or a TrustedHTML object if Trusted Types are supported).\n * If `WHOLE_DOCUMENT` is enabled a `HTMLHtmlElement` will be returned instead\n */\n let RETURN_DOM = false;\n /* Decide if a DOM `DocumentFragment` should be returned, instead of a html\n * string (or a TrustedHTML object if Trusted Types are supported) */\n let RETURN_DOM_FRAGMENT = false;\n /* Try to return a Trusted Type object instead of a string, return a string in\n * case Trusted Types are not supported */\n let RETURN_TRUSTED_TYPE = false;\n /* Output should be free from DOM clobbering attacks?\n * This sanitizes markups named with colliding, clobberable built-in DOM APIs.\n */\n let SANITIZE_DOM = true;\n /* Achieve full DOM Clobbering protection by isolating the namespace of named\n * properties and JS variables, mitigating attacks that abuse the HTML/DOM spec rules.\n *\n * HTML/DOM spec rules that enable DOM Clobbering:\n * - Named Access on Window (§7.3.3)\n * - DOM Tree Accessors (§3.1.5)\n * - Form Element Parent-Child Relations (§4.10.3)\n * - Iframe srcdoc / Nested WindowProxies (§4.8.5)\n * - HTMLCollection (§4.2.10.2)\n *\n * Namespace isolation is implemented by prefixing `id` and `name` attributes\n * with a constant string, i.e., `user-content-`\n */\n let SANITIZE_NAMED_PROPS = false;\n const SANITIZE_NAMED_PROPS_PREFIX = 'user-content-';\n /* Keep element content when removing element? */\n let KEEP_CONTENT = true;\n /* If a `Node` is passed to sanitize(), then performs sanitization in-place instead\n * of importing it into a new Document and returning a sanitized copy */\n let IN_PLACE = false;\n /* Allow usage of profiles like html, svg and mathMl */\n let USE_PROFILES = {};\n /* Tags to ignore content of when KEEP_CONTENT is true */\n let FORBID_CONTENTS = null;\n const DEFAULT_FORBID_CONTENTS = addToSet({}, ['annotation-xml', 'audio', 'colgroup', 'desc', 'foreignobject', 'head', 'iframe', 'math', 'mi', 'mn', 'mo', 'ms', 'mtext', 'noembed', 'noframes', 'noscript', 'plaintext', 'script', 'style', 'svg', 'template', 'thead', 'title', 'video', 'xmp']);\n /* Tags that are safe for data: URIs */\n let DATA_URI_TAGS = null;\n const DEFAULT_DATA_URI_TAGS = addToSet({}, ['audio', 'video', 'img', 'source', 'image', 'track']);\n /* Attributes safe for values like \"javascript:\" */\n let URI_SAFE_ATTRIBUTES = null;\n const DEFAULT_URI_SAFE_ATTRIBUTES = addToSet({}, ['alt', 'class', 'for', 'id', 'label', 'name', 'pattern', 'placeholder', 'role', 'summary', 'title', 'value', 'style', 'xmlns']);\n const MATHML_NAMESPACE = 'http://www.w3.org/1998/Math/MathML';\n const SVG_NAMESPACE = 'http://www.w3.org/2000/svg';\n const HTML_NAMESPACE = 'http://www.w3.org/1999/xhtml';\n /* Document namespace */\n let NAMESPACE = HTML_NAMESPACE;\n let IS_EMPTY_INPUT = false;\n /* Allowed XHTML+XML namespaces */\n let ALLOWED_NAMESPACES = null;\n const DEFAULT_ALLOWED_NAMESPACES = addToSet({}, [MATHML_NAMESPACE, SVG_NAMESPACE, HTML_NAMESPACE], stringToString);\n let MATHML_TEXT_INTEGRATION_POINTS = addToSet({}, ['mi', 'mo', 'mn', 'ms', 'mtext']);\n let HTML_INTEGRATION_POINTS = addToSet({}, ['annotation-xml']);\n // Certain elements are allowed in both SVG and HTML\n // namespace. We need to specify them explicitly\n // so that they don't get erroneously deleted from\n // HTML namespace.\n const COMMON_SVG_AND_HTML_ELEMENTS = addToSet({}, ['title', 'style', 'font', 'a', 'script']);\n /* Parsing of strict XHTML documents */\n let PARSER_MEDIA_TYPE = null;\n const SUPPORTED_PARSER_MEDIA_TYPES = ['application/xhtml+xml', 'text/html'];\n const DEFAULT_PARSER_MEDIA_TYPE = 'text/html';\n let transformCaseFunc = null;\n /* Keep a reference to config to pass to hooks */\n let CONFIG = null;\n /* Ideally, do not touch anything below this line */\n /* ______________________________________________ */\n const formElement = document.createElement('form');\n const isRegexOrFunction = function isRegexOrFunction(testValue) {\n return testValue instanceof RegExp || testValue instanceof Function;\n };\n /**\n * _parseConfig\n *\n * @param cfg optional config literal\n */\n // eslint-disable-next-line complexity\n const _parseConfig = function _parseConfig() {\n let cfg = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};\n if (CONFIG && CONFIG === cfg) {\n return;\n }\n /* Shield configuration object from tampering */\n if (!cfg || typeof cfg !== 'object') {\n cfg = {};\n }\n /* Shield configuration object from prototype pollution */\n cfg = clone(cfg);\n PARSER_MEDIA_TYPE =\n // eslint-disable-next-line unicorn/prefer-includes\n SUPPORTED_PARSER_MEDIA_TYPES.indexOf(cfg.PARSER_MEDIA_TYPE) === -1 ? DEFAULT_PARSER_MEDIA_TYPE : cfg.PARSER_MEDIA_TYPE;\n // HTML tags and attributes are not case-sensitive, converting to lowercase. Keeping XHTML as is.\n transformCaseFunc = PARSER_MEDIA_TYPE === 'application/xhtml+xml' ? stringToString : stringToLowerCase;\n /* Set configuration parameters */\n ALLOWED_TAGS = objectHasOwnProperty(cfg, 'ALLOWED_TAGS') ? addToSet({}, cfg.ALLOWED_TAGS, transformCaseFunc) : DEFAULT_ALLOWED_TAGS;\n ALLOWED_ATTR = objectHasOwnProperty(cfg, 'ALLOWED_ATTR') ? addToSet({}, cfg.ALLOWED_ATTR, transformCaseFunc) : DEFAULT_ALLOWED_ATTR;\n ALLOWED_NAMESPACES = objectHasOwnProperty(cfg, 'ALLOWED_NAMESPACES') ? addToSet({}, cfg.ALLOWED_NAMESPACES, stringToString) : DEFAULT_ALLOWED_NAMESPACES;\n URI_SAFE_ATTRIBUTES = objectHasOwnProperty(cfg, 'ADD_URI_SAFE_ATTR') ? addToSet(clone(DEFAULT_URI_SAFE_ATTRIBUTES), cfg.ADD_URI_SAFE_ATTR, transformCaseFunc) : DEFAULT_URI_SAFE_ATTRIBUTES;\n DATA_URI_TAGS = objectHasOwnProperty(cfg, 'ADD_DATA_URI_TAGS') ? addToSet(clone(DEFAULT_DATA_URI_TAGS), cfg.ADD_DATA_URI_TAGS, transformCaseFunc) : DEFAULT_DATA_URI_TAGS;\n FORBID_CONTENTS = objectHasOwnProperty(cfg, 'FORBID_CONTENTS') ? addToSet({}, cfg.FORBID_CONTENTS, transformCaseFunc) : DEFAULT_FORBID_CONTENTS;\n FORBID_TAGS = objectHasOwnProperty(cfg, 'FORBID_TAGS') ? addToSet({}, cfg.FORBID_TAGS, transformCaseFunc) : clone({});\n FORBID_ATTR = objectHasOwnProperty(cfg, 'FORBID_ATTR') ? addToSet({}, cfg.FORBID_ATTR, transformCaseFunc) : clone({});\n USE_PROFILES = objectHasOwnProperty(cfg, 'USE_PROFILES') ? cfg.USE_PROFILES : false;\n ALLOW_ARIA_ATTR = cfg.ALLOW_ARIA_ATTR !== false; // Default true\n ALLOW_DATA_ATTR = cfg.ALLOW_DATA_ATTR !== false; // Default true\n ALLOW_UNKNOWN_PROTOCOLS = cfg.ALLOW_UNKNOWN_PROTOCOLS || false; // Default false\n ALLOW_SELF_CLOSE_IN_ATTR = cfg.ALLOW_SELF_CLOSE_IN_ATTR !== false; // Default true\n SAFE_FOR_TEMPLATES = cfg.SAFE_FOR_TEMPLATES || false; // Default false\n SAFE_FOR_XML = cfg.SAFE_FOR_XML !== false; // Default true\n WHOLE_DOCUMENT = cfg.WHOLE_DOCUMENT || false; // Default false\n RETURN_DOM = cfg.RETURN_DOM || false; // Default false\n RETURN_DOM_FRAGMENT = cfg.RETURN_DOM_FRAGMENT || false; // Default false\n RETURN_TRUSTED_TYPE = cfg.RETURN_TRUSTED_TYPE || false; // Default false\n FORCE_BODY = cfg.FORCE_BODY || false; // Default false\n SANITIZE_DOM = cfg.SANITIZE_DOM !== false; // Default true\n SANITIZE_NAMED_PROPS = cfg.SANITIZE_NAMED_PROPS || false; // Default false\n KEEP_CONTENT = cfg.KEEP_CONTENT !== false; // Default true\n IN_PLACE = cfg.IN_PLACE || false; // Default false\n IS_ALLOWED_URI$1 = cfg.ALLOWED_URI_REGEXP || IS_ALLOWED_URI;\n NAMESPACE = cfg.NAMESPACE || HTML_NAMESPACE;\n MATHML_TEXT_INTEGRATION_POINTS = cfg.MATHML_TEXT_INTEGRATION_POINTS || MATHML_TEXT_INTEGRATION_POINTS;\n HTML_INTEGRATION_POINTS = cfg.HTML_INTEGRATION_POINTS || HTML_INTEGRATION_POINTS;\n CUSTOM_ELEMENT_HANDLING = cfg.CUSTOM_ELEMENT_HANDLING || {};\n if (cfg.CUSTOM_ELEMENT_HANDLING && isRegexOrFunction(cfg.CUSTOM_ELEMENT_HANDLING.tagNameCheck)) {\n CUSTOM_ELEMENT_HANDLING.tagNameCheck = cfg.CUSTOM_ELEMENT_HANDLING.tagNameCheck;\n }\n if (cfg.CUSTOM_ELEMENT_HANDLING && isRegexOrFunction(cfg.CUSTOM_ELEMENT_HANDLING.attributeNameCheck)) {\n CUSTOM_ELEMENT_HANDLING.attributeNameCheck = cfg.CUSTOM_ELEMENT_HANDLING.attributeNameCheck;\n }\n if (cfg.CUSTOM_ELEMENT_HANDLING && typeof cfg.CUSTOM_ELEMENT_HANDLING.allowCustomizedBuiltInElements === 'boolean') {\n CUSTOM_ELEMENT_HANDLING.allowCustomizedBuiltInElements = cfg.CUSTOM_ELEMENT_HANDLING.allowCustomizedBuiltInElements;\n }\n if (SAFE_FOR_TEMPLATES) {\n ALLOW_DATA_ATTR = false;\n }\n if (RETURN_DOM_FRAGMENT) {\n RETURN_DOM = true;\n }\n /* Parse profile info */\n if (USE_PROFILES) {\n ALLOWED_TAGS = addToSet({}, text);\n ALLOWED_ATTR = [];\n if (USE_PROFILES.html === true) {\n addToSet(ALLOWED_TAGS, html$1);\n addToSet(ALLOWED_ATTR, html);\n }\n if (USE_PROFILES.svg === true) {\n addToSet(ALLOWED_TAGS, svg$1);\n addToSet(ALLOWED_ATTR, svg);\n addToSet(ALLOWED_ATTR, xml);\n }\n if (USE_PROFILES.svgFilters === true) {\n addToSet(ALLOWED_TAGS, svgFilters);\n addToSet(ALLOWED_ATTR, svg);\n addToSet(ALLOWED_ATTR, xml);\n }\n if (USE_PROFILES.mathMl === true) {\n addToSet(ALLOWED_TAGS, mathMl$1);\n addToSet(ALLOWED_ATTR, mathMl);\n addToSet(ALLOWED_ATTR, xml);\n }\n }\n /* Merge configuration parameters */\n if (cfg.ADD_TAGS) {\n if (typeof cfg.ADD_TAGS === 'function') {\n EXTRA_ELEMENT_HANDLING.tagCheck = cfg.ADD_TAGS;\n } else {\n if (ALLOWED_TAGS === DEFAULT_ALLOWED_TAGS) {\n ALLOWED_TAGS = clone(ALLOWED_TAGS);\n }\n addToSet(ALLOWED_TAGS, cfg.ADD_TAGS, transformCaseFunc);\n }\n }\n if (cfg.ADD_ATTR) {\n if (typeof cfg.ADD_ATTR === 'function') {\n EXTRA_ELEMENT_HANDLING.attributeCheck = cfg.ADD_ATTR;\n } else {\n if (ALLOWED_ATTR === DEFAULT_ALLOWED_ATTR) {\n ALLOWED_ATTR = clone(ALLOWED_ATTR);\n }\n addToSet(ALLOWED_ATTR, cfg.ADD_ATTR, transformCaseFunc);\n }\n }\n if (cfg.ADD_URI_SAFE_ATTR) {\n addToSet(URI_SAFE_ATTRIBUTES, cfg.ADD_URI_SAFE_ATTR, transformCaseFunc);\n }\n if (cfg.FORBID_CONTENTS) {\n if (FORBID_CONTENTS === DEFAULT_FORBID_CONTENTS) {\n FORBID_CONTENTS = clone(FORBID_CONTENTS);\n }\n addToSet(FORBID_CONTENTS, cfg.FORBID_CONTENTS, transformCaseFunc);\n }\n if (cfg.ADD_FORBID_CONTENTS) {\n if (FORBID_CONTENTS === DEFAULT_FORBID_CONTENTS) {\n FORBID_CONTENTS = clone(FORBID_CONTENTS);\n }\n addToSet(FORBID_CONTENTS, cfg.ADD_FORBID_CONTENTS, transformCaseFunc);\n }\n /* Add #text in case KEEP_CONTENT is set to true */\n if (KEEP_CONTENT) {\n ALLOWED_TAGS['#text'] = true;\n }\n /* Add html, head and body to ALLOWED_TAGS in case WHOLE_DOCUMENT is true */\n if (WHOLE_DOCUMENT) {\n addToSet(ALLOWED_TAGS, ['html', 'head', 'body']);\n }\n /* Add tbody to ALLOWED_TAGS in case tables are permitted, see #286, #365 */\n if (ALLOWED_TAGS.table) {\n addToSet(ALLOWED_TAGS, ['tbody']);\n delete FORBID_TAGS.tbody;\n }\n if (cfg.TRUSTED_TYPES_POLICY) {\n if (typeof cfg.TRUSTED_TYPES_POLICY.createHTML !== 'function') {\n throw typeErrorCreate('TRUSTED_TYPES_POLICY configuration option must provide a \"createHTML\" hook.');\n }\n if (typeof cfg.TRUSTED_TYPES_POLICY.createScriptURL !== 'function') {\n throw typeErrorCreate('TRUSTED_TYPES_POLICY configuration option must provide a \"createScriptURL\" hook.');\n }\n // Overwrite existing TrustedTypes policy.\n trustedTypesPolicy = cfg.TRUSTED_TYPES_POLICY;\n // Sign local variables required by `sanitize`.\n emptyHTML = trustedTypesPolicy.createHTML('');\n } else {\n // Uninitialized policy, attempt to initialize the internal dompurify policy.\n if (trustedTypesPolicy === undefined) {\n trustedTypesPolicy = _createTrustedTypesPolicy(trustedTypes, currentScript);\n }\n // If creating the internal policy succeeded sign internal variables.\n if (trustedTypesPolicy !== null && typeof emptyHTML === 'string') {\n emptyHTML = trustedTypesPolicy.createHTML('');\n }\n }\n // Prevent further manipulation of configuration.\n // Not available in IE8, Safari 5, etc.\n if (freeze) {\n freeze(cfg);\n }\n CONFIG = cfg;\n };\n /* Keep track of all possible SVG and MathML tags\n * so that we can perform the namespace checks\n * correctly. */\n const ALL_SVG_TAGS = addToSet({}, [...svg$1, ...svgFilters, ...svgDisallowed]);\n const ALL_MATHML_TAGS = addToSet({}, [...mathMl$1, ...mathMlDisallowed]);\n /**\n * @param element a DOM element whose namespace is being checked\n * @returns Return false if the element has a\n * namespace that a spec-compliant parser would never\n * return. Return true otherwise.\n */\n const _checkValidNamespace = function _checkValidNamespace(element) {\n let parent = getParentNode(element);\n // In JSDOM, if we're inside shadow DOM, then parentNode\n // can be null. We just simulate parent in this case.\n if (!parent || !parent.tagName) {\n parent = {\n namespaceURI: NAMESPACE,\n tagName: 'template'\n };\n }\n const tagName = stringToLowerCase(element.tagName);\n const parentTagName = stringToLowerCase(parent.tagName);\n if (!ALLOWED_NAMESPACES[element.namespaceURI]) {\n return false;\n }\n if (element.namespaceURI === SVG_NAMESPACE) {\n // The only way to switch from HTML namespace to SVG\n // is via