diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 8635d58e2..9e1ad368e 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -1,2 +1,2 @@ # WIRIS INTEGRATIONS TEAM -* @carla-at-wiris @jgonzalez-at-wiris @usantos-at-wiris @xjiang-at-wiris @ncornaglia-at-wiris +* @carla-at-wiris @jgonzalez-at-wiris @xjiang-at-wiris @ncornaglia-at-wiris diff --git a/tests/e2e/enums/track_changes_options.ts b/tests/e2e/enums/track_changes_options.ts new file mode 100644 index 000000000..8402774fb --- /dev/null +++ b/tests/e2e/enums/track_changes_options.ts @@ -0,0 +1,9 @@ +enum TrackChangesOptions { + ACCEPT_ALL = 'Accept all suggestions', + ACCEPT_SELECTED = 'Accept all selected suggestions', + DISCARD_ALL = 'Discard all suggestions', + DISCARD_SELECTED = 'Discard all selected suggestions', + PREVIEW_FINAL = 'Preview final content' +} + +export default TrackChangesOptions diff --git a/tests/e2e/enums/typing_mode.ts b/tests/e2e/enums/typing_mode.ts index 3eaa5bd5b..0369cbb59 100644 --- a/tests/e2e/enums/typing_mode.ts +++ b/tests/e2e/enums/typing_mode.ts @@ -4,4 +4,4 @@ enum TypingMode { UNKNOWN = 'UNKNOWN', } -export default TypingMode \ No newline at end of file +export default TypingMode diff --git a/tests/e2e/page-objects/base_editor.ts b/tests/e2e/page-objects/base_editor.ts index 40c852663..7a8240d9a 100644 --- a/tests/e2e/page-objects/base_editor.ts +++ b/tests/e2e/page-objects/base_editor.ts @@ -1,6 +1,7 @@ import { Page, Locator, expect, FrameLocator } from '@playwright/test' import Toolbar from '../enums/toolbar' import type Equation from '../interfaces/equation' +import TrackChangesOptions from '../enums/track_changes_options' import BasePage from './page' const path = require('path') @@ -105,6 +106,150 @@ export default abstract class BaseEditor extends BasePage { await this.page.keyboard.type(text) } + public async getSourceCodeEditFieldValue(): Promise { + const sourceCodeEditField = this.getSourceCodeEditField?.() + if (!sourceCodeEditField) { + throw new Error('Source code edit field selector is not defined.') + } + await this.page.locator(sourceCodeEditField).waitFor({ state: 'visible' }) + return await this.page.locator(sourceCodeEditField).inputValue() + } + + public async clickTrackChanges(): Promise { + const trackChangesButton = this.getTrackChangesButton?.() + if (trackChangesButton) { + await this.page.locator(trackChangesButton).first().waitFor({ state: 'visible' }) + await this.page.locator(trackChangesButton).first().click() + } + } + + /** + * Retrieves all track change suggestion markers from the editor area. + * Each item includes its type (insertion or deletion), the equation data, and the suggestion ID. + * @returns {Promise>} + */ + public async getTrackChangesItems(): Promise< + Array<{ + type: 'insertion' | 'deletion' + altText: string + mathml: string + suggestionId: string + authorId: string + }> + > { + let frameOrPage: Page | FrameLocator + if (this.iframe) { + frameOrPage = this.page.frameLocator(this.iframe) + } else { + frameOrPage = this.page + } + + await this.page.waitForTimeout(500) + + const suggestionMarkers = frameOrPage.locator( + `${this.editField} span.ck-suggestion-marker` + ) + const count = await suggestionMarkers.count() + const items: Array<{ + type: 'insertion' | 'deletion' + altText: string + mathml: string + suggestionId: string + authorId: string + }> = [] + + for (let i = 0; i < count; i++) { + const marker = suggestionMarkers.nth(i) + const classList = await marker.getAttribute('class') || '' + const suggestionId = await marker.getAttribute('data-suggestion') || '' + const authorId = await marker.getAttribute('data-author-id') || '' + + let type: 'insertion' | 'deletion' + if (classList.includes('ck-suggestion-marker-deletion')) { + type = 'deletion' + } else if (classList.includes('ck-suggestion-marker-insertion')) { + type = 'insertion' + } else { + // Default to insertion if neither specific class is found + type = 'insertion' + } + + // Get equation data from the img inside the marker + const img = marker.locator('img.Wirisformula') + const imgCount = await img.count() + + if (imgCount > 0) { + const altText = await img.first().getAttribute('alt') || '' + const mathml = await img.first().getAttribute('data-mathml') || '' + items.push({ type, altText, mathml, suggestionId, authorId }) + } else { + // Marker exists but has no equation image (could be text-only change) + const textContent = await marker.textContent() || '' + items.push({ type, altText: textContent, mathml: '', suggestionId, authorId }) + } + } + + return items + } + + /** + * Gets only the track change insertions from the editor. + */ + public async getTrackChangeInsertions() { + const items = await this.getTrackChangesItems() + return items.filter((item) => item.type === 'insertion') + } + + /** + * Gets only the track change deletions from the editor. + */ + public async getTrackChangeDeletions() { + const items = await this.getTrackChangesItems() + return items.filter((item) => item.type === 'deletion') + } + + /** + * Retrieves all equations from the track changes preview container. + * @returns {Promise} Array of equations found in the preview. + */ + public async getTrackChangesPreviewEquations(): Promise { + const previewContainer = this.getTrackChangesPreviewContainer?.() + if (!previewContainer) { + throw new Error('Track changes preview container selector is not defined.') + } + + await this.page.locator(previewContainer).waitFor({ state: 'visible' }) + + const images = this.page.locator(`${previewContainer} img.Wirisformula`) + const count = await images.count() + const equations: Equation[] = [] + + for (let i = 0; i < count; i++) { + const img = images.nth(i) + const altText = await img.getAttribute('alt') || '' + const mathml = await img.getAttribute('data-mathml') || '' + equations.push({ altText, mathml }) + } + + return equations + } + + /** + * Clicks on a specific track change option in the editor. + * @param option - The track change option to click, such as accepting or discarding suggestions. + */ + public async clickTrackChangeOption(option: TrackChangesOptions): Promise { + const trackChangesDropdown = this.getTrackChangesDropdown?.() + if (trackChangesDropdown) { + await this.page.locator(trackChangesDropdown).click() + } + + const trackChangeOptionButton = this.page.getByRole('menuitem', { name: option }) + if (trackChangeOptionButton) { + await trackChangeOptionButton.click() + } + } + /** * Retrieves all equations from the editor using the alt text and data-mathml DOM attributes. * @returns {Promise} Array of equation interface. @@ -154,7 +299,17 @@ export default abstract class BaseEditor extends BasePage { return this.page.frameLocator(this.iframe).locator(`${this.editField} img[alt="${equation.altText}"]`) } - return this.page.locator(`${this.editField} img[alt="${equation.altText}"]`) + return this.page.locator(`${this.editField} img[alt="${equation.altText}"]`).first() + } + + /** + * Deletes a specific equation from the editor. + * @param {Equation} equation - The equation to delete. + */ + public async deleteEquation(equation: Equation): Promise { + const equationElement = this.getEquationElement(equation) + await equationElement.click() + await this.page.keyboard.press('Delete') } /** @@ -203,7 +358,7 @@ export default abstract class BaseEditor extends BasePage { await this.page.keyboard.press('Control+End') await this.pause(500) - await this.page.keyboard.type(textToInsert) + await this.type(textToInsert) } /** @@ -211,23 +366,49 @@ export default abstract class BaseEditor extends BasePage { * Uses selectItemAtCursor, but that's not compatible with froala, so in that case does a click in the contextual toolbar * @param {Toolbar} toolbar - toolbar of the test */ - public async openWirisEditorForLastInsertedFormula(toolbar: Toolbar, equation: Equation): Promise { + public async openWirisEditorForLastInsertedFormula(toolbar: Toolbar, equation?: Equation): Promise { const isFroala = this.getName() === 'froala' + if (isFroala && !equation) { + if (!equation) { + throw new Error('Equation must be provided for Froala editor') + } + await this.openWirisEditorForFormula(toolbar, equation) + } else { + await this.selectItemAtCursor() + await this.openWirisEditor(toolbar) + } + } + + /** + * Open the wiris Editor to edit a specific formula by clicking on it and opening the wiris editor. + * @param {Toolbar} toolbar - toolbar of the test + * @param {Equation} equation - The equation to edit + */ + public async openWirisEditorForFormula(toolbar: Toolbar, equation: Equation): Promise { + const isFroala = this.getName() === 'froala' + const equationElement = this.getEquationElement(equation) if (isFroala) { - const equationElement = this.getEquationElement(equation) await equationElement.click() - const mathTypeButton = this.getContextualToolbarMathTypeButton?.() if (mathTypeButton) { await this.page.locator(mathTypeButton).click() } } else { - await this.selectItemAtCursor() + await equationElement.click() await this.openWirisEditor(toolbar) } } + /** + * Selects a formula in the editor by clicking on it. + * @param equation - The equation to select. + */ + public async selectFormula(equation: Equation): Promise { + const equationElement = this.getEquationElement(equation) + await equationElement.click() + } + /** * Selects the item at the current cursor position within the editor. This uses shift + the left arrow key to select. */ @@ -268,7 +449,7 @@ export default abstract class BaseEditor extends BasePage { frameOrPage = this.page } - const textContents = await frameOrPage.locator(this.editField).textContent() + const textContents = await frameOrPage.locator(this.editField).first().textContent() if (!textContents) { return undefined @@ -400,8 +581,8 @@ export default abstract class BaseEditor extends BasePage { await this.page.mouse.move(box.x - 10, box.y - 10) await this.pause(500) await this.page.mouse.up() - } } + } public async applyStyle(): Promise { await this.focus() @@ -477,4 +658,10 @@ export default abstract class BaseEditor extends BasePage { public getSourceCodeEditorButton?(): string public getSourceCodeEditField?(): string + + public getTrackChangesButton?(): string + + public getTrackChangesDropdown?(): string + + public getTrackChangesPreviewContainer?(): string } diff --git a/tests/e2e/page-objects/html/ckeditor5.ts b/tests/e2e/page-objects/html/ckeditor5.ts index a098a3164..6373007e8 100644 --- a/tests/e2e/page-objects/html/ckeditor5.ts +++ b/tests/e2e/page-objects/html/ckeditor5.ts @@ -1,11 +1,15 @@ import { Page } from '@playwright/test' import BaseEditor from '../base_editor' +import TrackChangesOptions from '../../enums/track_changes_options' class CKEditor5 extends BaseEditor { protected readonly wirisEditorButtonMathType = "[data-cke-tooltip-text='Insert a math equation - MathType']" protected readonly wirisEditorButtonChemType = "[data-cke-tooltip-text='Insert a chemistry formula - ChemType']" protected readonly sourceCodeEditorButton = "[data-cke-tooltip-text='Source']" protected readonly sourceCodeEditField = '.ck-source-editing-area' + protected readonly trackChangesButton = "[data-cke-tooltip-text='Track changes']" + protected readonly trackChangesDropdown = '.ck-splitbutton__arrow'; + protected readonly trackChangesPreviewContainer = '.ck-track-changes-preview__root-container'; protected readonly editField = '.ck-editor__editable' protected readonly name = 'ckeditor5' @@ -20,6 +24,18 @@ class CKEditor5 extends BaseEditor { public getSourceCodeEditField(): string { return this.sourceCodeEditField } + + public getTrackChangesButton(): string { + return this.trackChangesButton + } + + public getTrackChangesDropdown(): string { + return this.trackChangesDropdown + } + + public getTrackChangesPreviewContainer(): string { + return this.trackChangesPreviewContainer + } } -export default CKEditor5 \ No newline at end of file +export default CKEditor5 diff --git a/tests/e2e/page-objects/page.ts b/tests/e2e/page-objects/page.ts index 563b3bba2..49ce25bc1 100644 --- a/tests/e2e/page-objects/page.ts +++ b/tests/e2e/page-objects/page.ts @@ -13,4 +13,15 @@ export default class BasePage { async pause(milliseconds: number): Promise { await this.page.waitForTimeout(milliseconds) } + + public async press(key: string, options?: { times?: number }): Promise { + const times = options?.times || 1 + for (let i = 0; i < times; i++) { + await this.page.keyboard.press(key) + } + } + + public async type(text: string): Promise { + await this.page.keyboard.type(text) + } } diff --git a/tests/e2e/page-objects/wiris_editor.ts b/tests/e2e/page-objects/wiris_editor.ts index 436f0e60e..7253cd4dc 100644 --- a/tests/e2e/page-objects/wiris_editor.ts +++ b/tests/e2e/page-objects/wiris_editor.ts @@ -72,6 +72,10 @@ class WirisEditor extends BasePage { return this.page.locator('canvas.wrs_canvas') } + get handPreview(): Locator { + return this.page.locator('.wrs_previewImage') + } + /** * Checks if the wiris editor modal is open by checking for the presence of the modal window, cancel and insert buttons. */ @@ -204,10 +208,40 @@ class WirisEditor extends BasePage { public async getMode(): Promise { const title = await this.handModeButton.getAttribute('title') - return title === 'Go to handwritten mode' ? TypingMode.KEYBOARD : - title === 'Use keyboard' ? TypingMode.HAND : + return title === 'Go to handwritten mode' ? TypingMode.KEYBOARD : + title === 'Use keyboard' ? TypingMode.HAND : TypingMode.UNKNOWN } + + /** + * Draws a stroke on the hand canvas by simulating mouse movements. + * @param {Array<{x: number, y: number}>} points - Array of points to draw through (relative to canvas top-left corner) + */ + public async drawStroke(points: Array<{x: number, y: number}>): Promise { + if (points.length < 2) { + throw new Error('Need at least 2 points to draw a stroke') + } + + const canvas = this.handCanvas + const box = await canvas.boundingBox() + + if (!box) { + throw new Error('Canvas not found or not visible') + } + + // Move to first point and start drawing + await this.page.mouse.move(box.x + points[0].x, box.y + points[0].y) + await this.page.mouse.down() + + // Draw through each point + for (let i = 1; i < points.length; i++) { + await this.page.mouse.move(box.x + points[i].x, box.y + points[i].y) + await this.pause(10) // Small pause for smoother drawing + } + + // Release mouse + await this.page.mouse.up() + } } -export default WirisEditor \ No newline at end of file +export default WirisEditor diff --git a/tests/e2e/playwright.config.ts b/tests/e2e/playwright.config.ts index 04c386927..ee07fcc21 100644 --- a/tests/e2e/playwright.config.ts +++ b/tests/e2e/playwright.config.ts @@ -46,9 +46,9 @@ export default defineConfig({ ], use: { baseURL: process.env.USE_STAGING === 'true' ? 'https://integrations.wiris.kitchen' : '', - trace: isCI ? 'retain-on-failure' : 'on-first-retry', + trace: 'retain-on-failure', screenshot: 'only-on-failure', - video: isCI ? 'off' : 'on-first-retry', + video: isCI ? 'off' : 'retain-on-failure', }, webServer: process.env.USE_STAGING === 'true' ? undefined : webServers, projects: [ diff --git a/tests/e2e/tests/edit/edit_hand.spec.ts b/tests/e2e/tests/edit/edit_hand.spec.ts index f88c6234e..703321894 100644 --- a/tests/e2e/tests/edit/edit_hand.spec.ts +++ b/tests/e2e/tests/edit/edit_hand.spec.ts @@ -14,7 +14,7 @@ for (const editorName of editors) { for (const toolbar of toolbars) { test(`@smoke MTHTML-8 Edit Hand equation with ${toolbar}: ${editorName} editor`, async ({ page }) => { const { editor, wirisEditor } = await setupEditor(page, editorName) - + await editor.open() await editor.clear() await editor.openWirisEditor(toolbar) @@ -23,11 +23,26 @@ for (const editorName of editors) { await wirisEditor.pause(1000) // Wait for the equation to be processed await wirisEditor.handModeButton.click() await wirisEditor.pause(500) + await wirisEditor.drawStroke([ + { x: 300, y: 135 }, + { x: 350, y: 135 }, + ]); + await wirisEditor.pause(100) + await wirisEditor.drawStroke([ + { x: 325, y: 110 }, + { x: 325, y: 160 }, + ]); + await wirisEditor.pause(100) + await wirisEditor.drawStroke([ + { x: 380, y: 60 }, + { x: 380, y: 185 }, + ]); + await wirisEditor.pause(2000) // Wait for handwriting recognition to process the input, may neet to be improved await wirisEditor.insertButton.click() await wirisEditor.waitUntilClosed() - await editor.waitForEquation(Equations.singleNumber) + await editor.waitForEquation(Equations.OnePlusOne) - await editor.openWirisEditorForLastInsertedFormula(toolbar, Equations.singleNumber) + await editor.openWirisEditorForLastInsertedFormula(toolbar, Equations.OnePlusOne) await wirisEditor.waitUntilLoaded(TypingMode.HAND) const typingMode = await wirisEditor.getMode() @@ -35,4 +50,4 @@ for (const editorName of editors) { }) } }) -} \ No newline at end of file +} diff --git a/tests/e2e/tests/editor/editor.spec.ts b/tests/e2e/tests/editor/editor.spec.ts index d8020c934..2452f2c7e 100644 --- a/tests/e2e/tests/editor/editor.spec.ts +++ b/tests/e2e/tests/editor/editor.spec.ts @@ -11,9 +11,9 @@ for (const editorName of editors) { tag: [`@${editorName}`, '@regression'], }, () => { test.describe('Undo and Redo', () => { - const isKnownIssue = editorName === 'generic'; - test(`MTHTML-78 Undo and redo math formula with ${editorName} editor`, { tag: isKnownIssue ? ['@knownissue'] : [] } , async ({ page }) => { - test.fail(isKnownIssue, 'Known issue: generic editors fails to undo equation'); + const isGeneric = editorName === 'generic'; + test(`MTHTML-78 Undo and redo math formula with ${editorName} editor`, { tag: isGeneric ? ['@knownissue'] : [] } , async ({ page }) => { + test.fail(isGeneric, 'Known issue: generic editors fails to undo equation'); const { editor, wirisEditor } = await setupEditor(page, editorName) @@ -35,6 +35,8 @@ for (const editorName of editors) { test.describe('Resize', () => { test(`MTHTML-22 Formulas cannot be resized ${editorName} editor`, async ({ page }) => { + test.skip(editorName === 'generic', 'Known case: generic editor does not allow resizing formulas'); + const { editor, wirisEditor } = await setupEditor(page, editorName) await editor.open() @@ -104,7 +106,7 @@ for (const editorName of editors) { }) test.describe('Text Alignment', () => { - test(`MTHTML-23 Validate formula alignment: ${editorName} editor`, async ({ page }) => { + test.skip(`MTHTML-23 Validate formula alignment: ${editorName} editor`, async ({ page }) => { const { editor, wirisEditor } = await setupEditor(page, editorName) await editor.open() diff --git a/tests/e2e/tests/insert/insert_corner_cases.spec.ts b/tests/e2e/tests/insert/insert_corner_cases.spec.ts index 47e15595e..65de3dd84 100644 --- a/tests/e2e/tests/insert/insert_corner_cases.spec.ts +++ b/tests/e2e/tests/insert/insert_corner_cases.spec.ts @@ -14,7 +14,7 @@ for (const editorName of editors) { }, () => { test(`MTHTML-80 Insert styled equation: ${editorName} editor`, async ({ page }) => { const { editor, wirisEditor } = await setupEditor(page, editorName) - + await editor.open() await editor.openWirisEditor(toolbar) await wirisEditor.waitUntilLoaded() @@ -29,7 +29,7 @@ for (const editorName of editors) { test(`MTHTML-68 Insert equation with special characters: ${editorName} editor`, async ({ page }) => { const { editor, wirisEditor } = await setupEditor(page, editorName) - + await editor.open() await editor.openWirisEditor(toolbar) await wirisEditor.waitUntilLoaded() @@ -44,16 +44,17 @@ for (const editorName of editors) { test(`MTHTML-90 User inserts a formula when the editor input doesn't have focus: ${editorName} editor`, async ({ page }) => { const { editor, wirisEditor } = await setupEditor(page, editorName) - + // Insert a formula using only the keyboard (click MT button > type > tab to insert > enter) and keep writing await editor.open() await editor.openWirisEditor(toolbar) await wirisEditor.waitUntilLoaded() await wirisEditor.typeEquationViaKeyboard('1+1') - await page.keyboard.press('Tab') + await editor.focus() await wirisEditor.pause(500) - await page.keyboard.press('Enter') + await wirisEditor.insertButton.click() + await wirisEditor.waitUntilClosed() await editor.waitForEquation(Equations.OnePlusOne) @@ -63,4 +64,4 @@ for (const editorName of editors) { }) }) } -} \ No newline at end of file +} diff --git a/tests/e2e/tests/track-changes/equations_changes.spec.ts b/tests/e2e/tests/track-changes/equations_changes.spec.ts new file mode 100644 index 000000000..6b8510a50 --- /dev/null +++ b/tests/e2e/tests/track-changes/equations_changes.spec.ts @@ -0,0 +1,91 @@ +import { test, expect } from '@playwright/test' +import { setupEditor, getEditorsFromEnv } from '../../helpers/test-setup' +import Equations from '../../enums/equations' +import Toolbar from '../../enums/toolbar' + +const editors = getEditorsFromEnv() +const toolbars = Object.values(Toolbar) + +for (const editorName of editors) { + for (const toolbar of toolbars) { + test.describe(`Track Changes - ${editorName} editor`, { + tag: [`@${editorName}`, '@regression'], + }, () => { + test(`Insert equation - ${toolbar} toolbar`, async ({ page }) => { + const { editor, wirisEditor } = await setupEditor(page, editorName); + + const hasTrackChanges = editor.getTrackChangesButton !== undefined; + test.skip(!hasTrackChanges, `Track changes button not available in ${editorName} editor`); + + await editor.open() + await editor.clear() + await editor.clickTrackChanges() + await editor.openWirisEditor(toolbar) + await wirisEditor.waitUntilLoaded() + await wirisEditor.typeEquationUsingEntryForm(Equations.singleNumber.mathml) + await wirisEditor.insertButton.click() + await wirisEditor.waitUntilClosed() + await editor.waitForEquation(Equations.singleNumber) + + await editor.getTrackChangesItems().then(items => { + const hasInsertedEquationInTrackChanges = items.some((item) => item.type === 'insertion' && item.altText === Equations.singleNumber.altText); + expect(hasInsertedEquationInTrackChanges).toBeTruthy() + }) + }) + + test(`Delete equation - ${toolbar} toolbar`, async ({ page }) => { + const { editor, wirisEditor } = await setupEditor(page, editorName); + + const hasTrackChanges = editor.getTrackChangesButton !== undefined; + test.skip(!hasTrackChanges, `Track changes button not available in ${editorName} editor`); + + await editor.open() + await editor.clear() + await editor.openWirisEditor(toolbar) + await wirisEditor.waitUntilLoaded() + await wirisEditor.typeEquationUsingEntryForm(Equations.singleNumber.mathml) + await wirisEditor.insertButton.click() + await wirisEditor.waitUntilClosed() + await editor.waitForEquation(Equations.singleNumber) + + await editor.clickTrackChanges() + await editor.deleteEquation(Equations.singleNumber) + + await editor.getTrackChangesItems().then(items => { + const hasDeletedEquationInTrackChanges = items.some((item) => item.type === 'deletion' && item.altText === Equations.singleNumber.altText); + expect(hasDeletedEquationInTrackChanges).toBeTruthy() + }) + }) + + test(`Edit equation - ${toolbar} toolbar`, async ({ page }) => { + const { editor, wirisEditor } = await setupEditor(page, editorName); + + const hasTrackChanges = editor.getTrackChangesButton !== undefined; + test.skip(!hasTrackChanges, `Track changes button not available in ${editorName} editor`); + + await editor.open() + await editor.clear() + await editor.openWirisEditor(toolbar) + await wirisEditor.waitUntilLoaded() + await wirisEditor.typeEquationUsingEntryForm(Equations.singleNumber.mathml) + await wirisEditor.insertButton.click() + await wirisEditor.waitUntilClosed() + await editor.waitForEquation(Equations.singleNumber) + + await editor.clickTrackChanges() + await editor.openWirisEditorForLastInsertedFormula(toolbar, Equations.singleNumber) + await wirisEditor.waitUntilLoaded() + await wirisEditor.typeEquationViaKeyboard('+1') + await wirisEditor.insertButton.click() + await wirisEditor.waitUntilClosed() + + await editor.getTrackChangesItems().then(items => { + const hasEditedEquationInTrackChanges = items.some((item) => item.type === 'insertion' && item.altText === Equations.OnePlusOne.altText); + const hasDeletedEquationInTrackChanges = items.some((item) => item.type === 'deletion' && item.altText === Equations.singleNumber.altText); + expect(hasEditedEquationInTrackChanges).toBeTruthy() + expect(hasDeletedEquationInTrackChanges).toBeTruthy() + }) + }) + }) + } +} diff --git a/tests/e2e/tests/track-changes/latex_changes.spec.ts b/tests/e2e/tests/track-changes/latex_changes.spec.ts new file mode 100644 index 000000000..fafa757de --- /dev/null +++ b/tests/e2e/tests/track-changes/latex_changes.spec.ts @@ -0,0 +1,71 @@ +import { test, expect } from '@playwright/test' +import { setupEditor, getEditorsFromEnv } from '../../helpers/test-setup' +import Equations from '../../enums/equations' +import Toolbar from '../../enums/toolbar' +import Equation from '../../interfaces/equation' +import TrackChangesOptions from '../../enums/track_changes_options' + +const editors = getEditorsFromEnv() +const toolbars = Object.values(Toolbar) + +for (const editorName of editors) { + for (const toolbar of toolbars) { + test.describe(`Track Changes Latex - ${editorName} editor`, { + tag: [`@${editorName}`, '@regression'], + }, () => { + test(`Edit existing latex equation via MT/CT - ${toolbar} toolbar`, async ({ page }) => { + const { editor, wirisEditor } = await setupEditor(page, editorName); + + const hasTrackChanges = editor.getTrackChangesButton !== undefined; + test.skip(!hasTrackChanges, `Track changes button not available in ${editorName} editor`); + + await editor.open() + await editor.clear() + await editor.appendText('$$ ' + Equations.squareRootY.latex + ' $$') + + await editor.clickTrackChanges() + + await editor.openWirisEditor(toolbar) + await wirisEditor.waitUntilLoaded() + await wirisEditor.type('+5') + await wirisEditor.insertButton.click() + await wirisEditor.waitUntilClosed() + + await editor.getLatexEquationsInEditField().then((latexEquations) => { + let hasEditedEquationInTrackChanges = false; + + if (latexEquations) { + hasEditedEquationInTrackChanges = latexEquations.some((latex) => latex.includes(Equations.squareRootYPlusFive.latex!)); + } else { + hasEditedEquationInTrackChanges = false; + } + expect(hasEditedEquationInTrackChanges).toBeTruthy() + }) + }) + + test(`Edit existing latex equation manually and via MT/CT - ${toolbar} toolbar`, async ({ page }) => { + const { editor, wirisEditor } = await setupEditor(page, editorName); + + const hasTrackChanges = editor.getTrackChangesButton !== undefined; + test.skip(!hasTrackChanges, `Track changes button not available in ${editorName} editor`); + + await editor.open() + await editor.clear() + await editor.appendText('$$' + Equations.squareRootY.latex + '$$') + + await editor.clickTrackChanges() + + await editor.press('ArrowLeft', { times: 2 }) + await editor.type('+') + + await editor.openWirisEditor(toolbar) + await wirisEditor.waitUntilLoaded() + await wirisEditor.type('5') + await wirisEditor.insertButton.click() + await wirisEditor.waitUntilClosed() + + await editor.waitForLatexExpression(Equations.squareRootYPlusFive.latex!) + }); + }); + } +} diff --git a/tests/e2e/tests/track-changes/toolbar_changes.spec.ts b/tests/e2e/tests/track-changes/toolbar_changes.spec.ts new file mode 100644 index 000000000..4b92dcfc5 --- /dev/null +++ b/tests/e2e/tests/track-changes/toolbar_changes.spec.ts @@ -0,0 +1,315 @@ +import { test, expect } from '@playwright/test' +import { setupEditor, getEditorsFromEnv } from '../../helpers/test-setup' +import Equations from '../../enums/equations' +import Toolbar from '../../enums/toolbar' +import Equation from '../../interfaces/equation' +import TrackChangesOptions from '../../enums/track_changes_options' + +const editors = getEditorsFromEnv() +const toolbars = Object.values(Toolbar) + +for (const editorName of editors) { + for (const toolbar of toolbars) { + test.describe(`Track Changes Options - ${editorName} editor`, { + tag: [`@${editorName}`, '@regression'], + }, () => { + test(`Accept all suggestions - ${toolbar} toolbar`, async ({ page }) => { + const { editor, wirisEditor } = await setupEditor(page, editorName); + + const hasTrackChanges = editor.getTrackChangesButton !== undefined; + test.skip(!hasTrackChanges, `Track changes button not available in ${editorName} editor`); + + await editor.open() + await editor.clear() + await editor.openWirisEditor(toolbar) + await wirisEditor.waitUntilLoaded() + await wirisEditor.typeEquationUsingEntryForm(Equations.singleNumber.mathml) + await wirisEditor.insertButton.click() + await wirisEditor.waitUntilClosed() + await editor.waitForEquation(Equations.singleNumber) + + await editor.press('Enter') + await editor.openWirisEditor(toolbar) + await wirisEditor.waitUntilLoaded() + await wirisEditor.typeEquationUsingEntryForm(Equations.OnePlusOne.mathml) + await wirisEditor.insertButton.click() + await wirisEditor.waitUntilClosed() + await editor.waitForEquation(Equations.OnePlusOne) + + await editor.clickTrackChanges() + + await editor.press('Enter') + await editor.openWirisEditor(toolbar) + await wirisEditor.waitUntilLoaded() + await wirisEditor.typeEquationUsingEntryForm(Equations.styledOnePlusOne.mathml) + await wirisEditor.insertButton.click() + await wirisEditor.waitUntilClosed() + + await editor.deleteEquation(Equations.OnePlusOne) + + await editor.openWirisEditorForFormula(toolbar, Equations.singleNumber) + await wirisEditor.waitUntilLoaded() + await wirisEditor.typeEquationViaKeyboard('+1') + await wirisEditor.insertButton.click() + await wirisEditor.waitUntilClosed() + + await editor.clickTrackChangeOption(TrackChangesOptions.ACCEPT_ALL) + + await editor.getEquations().then((equations: Equation[]) => { + const hasSingleNumber = equations.some((equation: Equation) => equation.altText === Equations.singleNumber.altText) + const hasOnePlusOne = equations.some((equation: Equation) => equation.altText === Equations.OnePlusOne.altText) + const hasStyledOnePlusOne = equations.some((equation: Equation) => equation.altText === Equations.styledOnePlusOne.altText) + expect(hasSingleNumber).toBeFalsy() + expect(hasOnePlusOne).toBeTruthy() + expect(hasStyledOnePlusOne).toBeTruthy() + expect(equations.length).toBe(2) + }) + expect(await editor.getTrackChangesItems()).toEqual([]) + }) + + test(`Discard all suggestions - ${toolbar} toolbar`, async ({ page }) => { + const { editor, wirisEditor } = await setupEditor(page, editorName); + + const hasTrackChanges = editor.getTrackChangesButton !== undefined; + test.skip(!hasTrackChanges, `Track changes button not available in ${editorName} editor`); + + await editor.open() + await editor.clear() + await editor.openWirisEditor(toolbar) + await wirisEditor.waitUntilLoaded() + await wirisEditor.typeEquationUsingEntryForm(Equations.singleNumber.mathml) + await wirisEditor.insertButton.click() + await wirisEditor.waitUntilClosed() + await editor.waitForEquation(Equations.singleNumber) + + await editor.press('Enter') + await editor.openWirisEditor(toolbar) + await wirisEditor.waitUntilLoaded() + await wirisEditor.typeEquationUsingEntryForm(Equations.OnePlusOne.mathml) + await wirisEditor.insertButton.click() + await wirisEditor.waitUntilClosed() + await editor.waitForEquation(Equations.OnePlusOne) + + await editor.clickTrackChanges() + + await editor.press('Enter') + await editor.openWirisEditor(toolbar) + await wirisEditor.waitUntilLoaded() + await wirisEditor.typeEquationUsingEntryForm(Equations.styledOnePlusOne.mathml) + await wirisEditor.insertButton.click() + await wirisEditor.waitUntilClosed() + + await editor.deleteEquation(Equations.OnePlusOne) + + await editor.openWirisEditorForFormula(toolbar, Equations.singleNumber) + await wirisEditor.waitUntilLoaded() + await wirisEditor.typeEquationViaKeyboard('+1') + await wirisEditor.insertButton.click() + await wirisEditor.waitUntilClosed() + + await editor.clickTrackChangeOption(TrackChangesOptions.DISCARD_ALL) + + await editor.getEquations().then((equations: Equation[]) => { + const hasSingleNumber = equations.some((equation: Equation) => equation.altText === Equations.singleNumber.altText) + const hasOnePlusOne = equations.some((equation: Equation) => equation.altText === Equations.OnePlusOne.altText) + const hasStyledOnePlusOne = equations.some((equation: Equation) => equation.altText === Equations.styledOnePlusOne.altText) + expect(hasSingleNumber).toBeTruthy() + expect(hasOnePlusOne).toBeTruthy() + expect(hasStyledOnePlusOne).toBeFalsy() + expect(equations.length).toBe(2) + }) + expect(await editor.getTrackChangesItems()).toEqual([]) + }) + + test(`Accept all selected suggestions - ${toolbar} toolbar`, async ({ page }) => { + const { editor, wirisEditor } = await setupEditor(page, editorName); + + const hasTrackChanges = editor.getTrackChangesButton !== undefined; + test.skip(!hasTrackChanges, `Track changes button not available in ${editorName} editor`); + + await editor.open() + await editor.clear() + await editor.openWirisEditor(toolbar) + await wirisEditor.waitUntilLoaded() + await wirisEditor.typeEquationUsingEntryForm(Equations.singleNumber.mathml) + await wirisEditor.insertButton.click() + await wirisEditor.waitUntilClosed() + await editor.waitForEquation(Equations.singleNumber) + + await editor.press('Enter') + await editor.openWirisEditor(toolbar) + await wirisEditor.waitUntilLoaded() + await wirisEditor.typeEquationUsingEntryForm(Equations.OnePlusOne.mathml) + await wirisEditor.insertButton.click() + await wirisEditor.waitUntilClosed() + await editor.waitForEquation(Equations.OnePlusOne) + + await editor.clickTrackChanges() + + await editor.press('Enter') + await editor.openWirisEditor(toolbar) + await wirisEditor.waitUntilLoaded() + await wirisEditor.typeEquationUsingEntryForm(Equations.styledOnePlusOne.mathml) + await wirisEditor.insertButton.click() + await wirisEditor.waitUntilClosed() + + await editor.deleteEquation(Equations.OnePlusOne) + + await editor.openWirisEditorForFormula(toolbar, Equations.singleNumber) + await wirisEditor.waitUntilLoaded() + await wirisEditor.typeEquationViaKeyboard('+1') + await wirisEditor.insertButton.click() + await wirisEditor.waitUntilClosed() + + await editor.clickTrackChangeOption(TrackChangesOptions.ACCEPT_SELECTED) + + await editor.getEquations().then((equations: Equation[]) => { + const hasSingleNumber = equations.some((equation: Equation) => equation.altText === Equations.singleNumber.altText) + const hasOnePlusOne = equations.some((equation: Equation) => equation.altText === Equations.OnePlusOne.altText) + const hasStyledOnePlusOne = equations.some((equation: Equation) => equation.altText === Equations.styledOnePlusOne.altText) + expect(hasSingleNumber).toBeFalsy() + expect(hasOnePlusOne).toBeTruthy() + expect(hasStyledOnePlusOne).toBeTruthy() + expect(equations.length).toBe(3) + }) + await editor.getTrackChangesItems().then(items => { + const hasDeletedSingleNumberInTrackChanges = items.some((item) => item.type === 'deletion' && item.altText === Equations.singleNumber.altText); + const hasInsertedOnePlusOneInTrackChanges = items.some((item) => item.type === 'insertion' && item.altText === Equations.OnePlusOne.altText); + const hasDeletedOnePlusOneInTrackChanges = items.some((item) => item.type === 'deletion' && item.altText === Equations.OnePlusOne.altText); + const hasInsertedStyledOnePlusOneInTrackChanges = items.some((item) => item.type === 'insertion' && item.altText === Equations.styledOnePlusOne.altText); + expect(hasDeletedSingleNumberInTrackChanges).toBeFalsy() + expect(hasInsertedOnePlusOneInTrackChanges).toBeFalsy() + expect(hasDeletedOnePlusOneInTrackChanges).toBeTruthy() + expect(hasInsertedStyledOnePlusOneInTrackChanges).toBeTruthy() + expect(items.length).toBe(2) + }) + }) + + test(`Discard all selected suggestions - ${toolbar} toolbar`, async ({ page }) => { + const { editor, wirisEditor } = await setupEditor(page, editorName); + + const hasTrackChanges = editor.getTrackChangesButton !== undefined; + test.skip(!hasTrackChanges, `Track changes button not available in ${editorName} editor`); + + await editor.open() + await editor.clear() + await editor.openWirisEditor(toolbar) + await wirisEditor.waitUntilLoaded() + await wirisEditor.typeEquationUsingEntryForm(Equations.singleNumber.mathml) + await wirisEditor.insertButton.click() + await wirisEditor.waitUntilClosed() + await editor.waitForEquation(Equations.singleNumber) + + await editor.press('Enter') + await editor.openWirisEditor(toolbar) + await wirisEditor.waitUntilLoaded() + await wirisEditor.typeEquationUsingEntryForm(Equations.OnePlusOne.mathml) + await wirisEditor.insertButton.click() + await wirisEditor.waitUntilClosed() + await editor.waitForEquation(Equations.OnePlusOne) + + await editor.clickTrackChanges() + + await editor.press('Enter') + await editor.openWirisEditor(toolbar) + await wirisEditor.waitUntilLoaded() + await wirisEditor.typeEquationUsingEntryForm(Equations.styledOnePlusOne.mathml) + await wirisEditor.insertButton.click() + await wirisEditor.waitUntilClosed() + + await editor.deleteEquation(Equations.OnePlusOne) + + await editor.openWirisEditorForFormula(toolbar, Equations.singleNumber) + await wirisEditor.waitUntilLoaded() + await wirisEditor.typeEquationViaKeyboard('+1') + await wirisEditor.insertButton.click() + await wirisEditor.waitUntilClosed() + + await editor.clickTrackChangeOption(TrackChangesOptions.DISCARD_SELECTED) + + await editor.getEquations().then((equations: Equation[]) => { + const hasSingleNumber = equations.some((equation: Equation) => equation.altText === Equations.singleNumber.altText) + const hasOnePlusOne = equations.some((equation: Equation) => equation.altText === Equations.OnePlusOne.altText) + const hasStyledOnePlusOne = equations.some((equation: Equation) => equation.altText === Equations.styledOnePlusOne.altText) + expect(hasSingleNumber).toBeTruthy() + expect(hasOnePlusOne).toBeTruthy() + expect(hasStyledOnePlusOne).toBeTruthy() + expect(equations.length).toBe(3) + }) + await editor.getTrackChangesItems().then(items => { + const hasDeletedSingleNumberInTrackChanges = items.some((item) => item.type === 'deletion' && item.altText === Equations.singleNumber.altText); + const hasInsertedOnePlusOneInTrackChanges = items.some((item) => item.type === 'insertion' && item.altText === Equations.OnePlusOne.altText); + const hasDeletedOnePlusOneInTrackChanges = items.some((item) => item.type === 'deletion' && item.altText === Equations.OnePlusOne.altText); + const hasInsertedStyledOnePlusOneInTrackChanges = items.some((item) => item.type === 'insertion' && item.altText === Equations.styledOnePlusOne.altText); + expect(hasDeletedSingleNumberInTrackChanges).toBeFalsy() + expect(hasInsertedOnePlusOneInTrackChanges).toBeFalsy() + expect(hasDeletedOnePlusOneInTrackChanges).toBeTruthy() + expect(hasInsertedStyledOnePlusOneInTrackChanges).toBeTruthy() + expect(items.length).toBe(2) + }) + }) + + test(`Preview final result - ${toolbar} toolbar`, async ({ page }) => { + const { editor, wirisEditor } = await setupEditor(page, editorName); + + const hasTrackChanges = editor.getTrackChangesButton !== undefined; + test.skip(!hasTrackChanges, `Track changes button not available in ${editorName} editor`); + + await editor.open() + await editor.clear() + await editor.openWirisEditor(toolbar) + await wirisEditor.waitUntilLoaded() + await wirisEditor.typeEquationUsingEntryForm(Equations.singleNumber.mathml) + await wirisEditor.insertButton.click() + await wirisEditor.waitUntilClosed() + await editor.waitForEquation(Equations.singleNumber) + + await editor.press('Enter') + await editor.openWirisEditor(toolbar) + await wirisEditor.waitUntilLoaded() + await wirisEditor.typeEquationUsingEntryForm(Equations.OnePlusOne.mathml) + await wirisEditor.insertButton.click() + await wirisEditor.waitUntilClosed() + await editor.waitForEquation(Equations.OnePlusOne) + + await editor.clickTrackChanges() + + await editor.press('Enter') + await editor.openWirisEditor(toolbar) + await wirisEditor.waitUntilLoaded() + await wirisEditor.typeEquationUsingEntryForm(Equations.styledOnePlusOne.mathml) + await wirisEditor.insertButton.click() + await wirisEditor.waitUntilClosed() + + await editor.deleteEquation(Equations.OnePlusOne) + + await editor.openWirisEditorForFormula(toolbar, Equations.singleNumber) + await wirisEditor.waitUntilLoaded() + await wirisEditor.typeEquationViaKeyboard('+1') + await wirisEditor.insertButton.click() + await wirisEditor.waitUntilClosed() + + await editor.clickTrackChangeOption(TrackChangesOptions.PREVIEW_FINAL) + + await editor.getEquations().then((equations: Equation[]) => { + const hasSingleNumber = equations.some((equation: Equation) => equation.altText === Equations.singleNumber.altText) + const hasOnePlusOne = equations.some((equation: Equation) => equation.altText === Equations.OnePlusOne.altText) + const hasStyledOnePlusOne = equations.some((equation: Equation) => equation.altText === Equations.styledOnePlusOne.altText) + expect(hasSingleNumber).toBeTruthy() + expect(hasOnePlusOne).toBeTruthy() + expect(hasStyledOnePlusOne).toBeTruthy() + expect(equations.length).toBe(6) + }) + await editor.getTrackChangesPreviewEquations().then(items => { + const hasSingleNumberInPreviewTrackChanges = items.some((item) => item.altText === Equations.singleNumber.altText); + const hasOnePlusOneInPreviewTrackChanges = items.some((item) => item.altText === Equations.OnePlusOne.altText); + const hasStyledOnePlusOneInPreviewTrackChanges = items.some((item) => item.altText === Equations.styledOnePlusOne.altText); + expect(hasSingleNumberInPreviewTrackChanges).toBeFalsy() + expect(hasOnePlusOneInPreviewTrackChanges).toBeTruthy() + expect(hasStyledOnePlusOneInPreviewTrackChanges).toBeTruthy() + expect(items.length).toBe(2) + }) + }) + }) + } +}