diff --git a/src/tests/frontend-new/helper/padHelper.ts b/src/tests/frontend-new/helper/padHelper.ts index b8e4244ec..517365c03 100644 --- a/src/tests/frontend-new/helper/padHelper.ts +++ b/src/tests/frontend-new/helper/padHelper.ts @@ -128,6 +128,8 @@ export const goToPad = async (page: Page, padId: string) => { export const clearPadContent = async (page: Page) => { + const body = await getPadBody(page); + await body.click(); await page.keyboard.down('Control'); await page.keyboard.press('A'); await page.keyboard.up('Control'); diff --git a/src/tests/frontend-new/helper/timeslider.ts b/src/tests/frontend-new/helper/timeslider.ts new file mode 100644 index 000000000..e193048e0 --- /dev/null +++ b/src/tests/frontend-new/helper/timeslider.ts @@ -0,0 +1,20 @@ +import {Page} from "@playwright/test"; + +/** + * Sets the src-attribute of the main iframe to the timeslider + * In case a revision is given, sets the timeslider to this specific revision. + * Defaults to going to the last revision. + * It waits until the timer is filled with date and time, because it's one of the + * last things that happen during timeslider load + * + * @param page + * @param {number} [revision] the optional revision + * @returns {Promise} + * @todo for some reason this does only work the first time, you cannot + * goto rev 0 and then via the same method to rev 5. Use buttons instead + */ +export const gotoTimeslider = async (page: Page, revision: number): Promise => { + let revisionString = Number.isInteger(revision) ? `#${revision}` : ''; + await page.goto(`${page.url()}/timeslider${revisionString}`); + await page.waitForSelector('#timer') +}; diff --git a/src/tests/frontend-new/specs/language.spec.ts b/src/tests/frontend-new/specs/language.spec.ts new file mode 100644 index 000000000..79acb5412 --- /dev/null +++ b/src/tests/frontend-new/specs/language.spec.ts @@ -0,0 +1,85 @@ +import {expect, test} from "@playwright/test"; +import {getPadBody, goToNewPad} from "../helper/padHelper"; +import {showSettings} from "../helper/settingsHelper"; + +test.beforeEach(async ({ page, browser })=>{ + const context = await browser.newContext() + await context.clearCookies() + await goToNewPad(page); +}) + + + +test.describe('Language select and change', function () { + + // Destroy language cookies + test('makes text german', async function ({page}) { + // click on the settings button to make settings visible + await showSettings(page) + + // click the language button + await page.locator('.nice-select').nth(1).locator('.current').click() + await page.locator('.nice-select').locator('[data-value=de]').click() + //const $language = chrome$('#languagemenu'); + //const $languageoption = $language.find('[value=de]'); + + // select german + await page.locator('.buttonicon-bold').evaluate((el) => el.parentElement!.title === 'Fett (Strg-B)'); + }); + + test('makes text English', async function ({page}) { + + await showSettings(page) + + // click the language button + await page.locator('.nice-select').nth(1).locator('.current').click() + await page.locator('.nice-select').locator('[data-value=de]').click() + + // select german + await page.locator('.buttonicon-bold').evaluate((el) => el.parentElement!.title === 'Fett (Strg-B)'); + + + // change to english + await page.locator('.nice-select').nth(1).locator('.current').click() + await page.locator('.nice-select').locator('[data-value=en]').click() + + // check if the language is now English + await page.locator('.buttonicon-bold').evaluate((el) => el.parentElement!.title !== 'Fett (Strg-B)'); + }); + + test('changes direction when picking an rtl lang', async function ({page}) { + + await showSettings(page) + + // click the language button + await page.locator('.nice-select').nth(1).locator('.current').click() + await page.locator('.nice-select').locator('[data-value=de]').click() + + // select german + await page.locator('.buttonicon-bold').evaluate((el) => el.parentElement!.title === 'Fett (Strg-B)'); + + // click the language button + await page.locator('.nice-select').nth(1).locator('.current').click() + // select arabic + // $languageoption.attr('selected','selected'); // Breaks the test.. + await page.locator('.nice-select').locator('[data-value=ar]').click() + + await page.waitForSelector('html[dir="rtl"]') + }); + + test('changes direction when picking an ltr lang', async function ({page}) { + await showSettings(page) + + + // change to english + await page.locator('.nice-select').nth(1).locator('.current').click() + await page.locator('.nice-select').locator('[data-value=en]').click() + + // check if the language is now English + await page.locator('.buttonicon-bold').evaluate((el) => el.parentElement!.title !== 'Fett (Strg-B)'); + + + await page.waitForSelector('html[dir="ltr"]') + + }); +}); diff --git a/src/tests/frontend-new/specs/timeslider.spec.ts b/src/tests/frontend-new/specs/timeslider.spec.ts new file mode 100644 index 000000000..317398f18 --- /dev/null +++ b/src/tests/frontend-new/specs/timeslider.spec.ts @@ -0,0 +1,37 @@ +import {expect, test} from "@playwright/test"; +import {clearPadContent, getPadBody, goToNewPad, writeToPad} from "../helper/padHelper"; + +test.beforeEach(async ({ page })=>{ + // create a new pad before each test run + await goToNewPad(page); +}) + + +// deactivated, we need a nice way to get the timeslider, this is ugly +test.describe('timeslider button takes you to the timeslider of a pad', function () { + + test('timeslider contained in URL', async function ({page}) { + const padBody = await getPadBody(page); + await clearPadContent(page) + await writeToPad(page, 'Foo'); // send line 1 to the pad + + // get the first text element inside the editable space + const $firstTextElement = padBody.locator('div span').first(); + const originalValue = await $firstTextElement.textContent(); // get the original value + await $firstTextElement.click() + await writeToPad(page, 'Testing'); // send line 1 to the pad + + const modifiedValue = await $firstTextElement.textContent(); // get the modified value + expect(modifiedValue).not.toBe(originalValue); // expect the value to change + + const $timesliderButton = page.locator('.buttonicon-history'); + await $timesliderButton.click(); // So click the timeslider link + + await page.waitForSelector('#timeslider-wrapper') + + const iFrameURL = page.url(); // get the url + const inTimeslider = iFrameURL.indexOf('timeslider') !== -1; + + expect(inTimeslider).toBe(true); // expect the value to change + }); +}); diff --git a/src/tests/frontend-new/specs/timeslider_follow.spec.ts b/src/tests/frontend-new/specs/timeslider_follow.spec.ts new file mode 100644 index 000000000..6fea11d23 --- /dev/null +++ b/src/tests/frontend-new/specs/timeslider_follow.spec.ts @@ -0,0 +1,80 @@ +'use strict'; +import {expect, Page, test} from "@playwright/test"; +import {clearPadContent, getPadBody, goToNewPad, writeToPad} from "../helper/padHelper"; +import {gotoTimeslider} from "../helper/timeslider"; + +test.beforeEach(async ({ page })=>{ + await goToNewPad(page); +}) + + +test.describe('timeslider follow', function () { + + // TODO needs test if content is also followed, when user a makes edits + // while user b is in the timeslider + test("content as it's added to timeslider", async function ({page}) { + // send 6 revisions + const revs = 6; + const message = 'a\n\n\n\n\n\n\n\n\n\n'; + const newLines = message.split('\n').length; + for (let i = 0; i < revs; i++) { + await writeToPad(page, message) + } + + await gotoTimeslider(page,0); + expect(page.url()).toContain('#0'); + + const originalTop = await page.evaluate(() => { + return window.document.querySelector('#innerdocbody')!.getBoundingClientRect().top; + }); + + // set to follow contents as it arrives + await page.check('#options-followContents'); + await page.click('#playpause_button_icon'); + + // wait for the scroll + await page.waitForTimeout(1000) + + const currentOffset = await page.evaluate(() => { + return window.document.querySelector('#innerdocbody')!.getBoundingClientRect().top; + }); + + + console.log('originalTop', originalTop); + console.log('currentOffset', currentOffset); + + expect(currentOffset).toBeLessThan(originalTop); + }); + + /** + * Tests for bug described in #4389 + * The goal is to scroll to the first line that contains a change right before + * the change is applied. + */ + test('only to lines that exist in the pad view, regression test for #4389', async function ({page}) { + const padBody = await getPadBody(page) + await padBody.click() + + await clearPadContent(page) + + await writeToPad(page,'Test line\n' + + '\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n' + + '\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n' + + '\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n'); + await padBody.locator('div').nth(40).click(); + await writeToPad(page, 'Another test line'); + + + await gotoTimeslider(page, 200); + + // set to follow contents as it arrives + await page.check('#options-followContents'); + + await page.waitForTimeout(1000) + + const oldYPosition = await page.locator('#editorcontainerbox').evaluate((el) => { + return el.scrollTop; + }) + expect(oldYPosition).toBe(0); + }); +}); diff --git a/src/tests/frontend/specs/language.js b/src/tests/frontend/specs/language.js deleted file mode 100644 index de3b483ec..000000000 --- a/src/tests/frontend/specs/language.js +++ /dev/null @@ -1,121 +0,0 @@ -'use strict'; - -describe('Language select and change', function () { - // Destroy language cookies - window.Cookies.remove('language'); - - // create a new pad before each test run - beforeEach(async function () { - await helper.aNewPad(); - }); - - // Destroy language cookies - it('makes text german', async function () { - this.timeout(1000); - const chrome$ = helper.padChrome$; - - // click on the settings button to make settings visible - const $settingsButton = chrome$('.buttonicon-settings'); - $settingsButton.trigger('click'); - - // click the language button - const $language = chrome$('#languagemenu'); - const $languageoption = $language.find('[value=de]'); - - // select german - $languageoption.attr('selected', 'selected'); - $language.trigger('change'); - - await helper.waitForPromise( - () => chrome$('.buttonicon-bold').parent()[0].title === 'Fett (Strg-B)'); - - // get the value of the bold button - const $boldButton = chrome$('.buttonicon-bold').parent(); - - // get the title of the bold button - const boldButtonTitle = $boldButton[0].title; - - // check if the language is now german - expect(boldButtonTitle).to.be('Fett (Strg-B)'); - }); - - it('makes text English', async function () { - this.timeout(1000); - const chrome$ = helper.padChrome$; - - // click on the settings button to make settings visible - const $settingsButton = chrome$('.buttonicon-settings'); - $settingsButton.trigger('click'); - - // click the language button - const $language = chrome$('#languagemenu'); - // select english - $language.val('en'); - $language.trigger('change'); - - // get the value of the bold button - let $boldButton = chrome$('.buttonicon-bold').parent(); - - await helper.waitForPromise(() => $boldButton[0].title !== 'Fett (Strg+B)'); - - // get the value of the bold button - $boldButton = chrome$('.buttonicon-bold').parent(); - - // get the title of the bold button - const boldButtonTitle = $boldButton[0].title; - - // check if the language is now English - expect(boldButtonTitle).to.be('Bold (Ctrl+B)'); - }); - - it('changes direction when picking an rtl lang', async function () { - // TODO: flaky - if (window.bowser.safari) { - this.timeout(5000); - } else { - this.timeout(1000); - } - const chrome$ = helper.padChrome$; - - // click on the settings button to make settings visible - const $settingsButton = chrome$('.buttonicon-settings'); - $settingsButton.trigger('click'); - - // click the language button - const $language = chrome$('#languagemenu'); - const $languageoption = $language.find('[value=ar]'); - - // select arabic - // $languageoption.attr('selected','selected'); // Breaks the test.. - $language.val('ar'); - $languageoption.trigger('change'); - - await helper.waitForPromise(() => chrome$('html')[0].dir !== 'ltr'); - - // check if the document's direction was changed - expect(chrome$('html')[0].dir).to.be('rtl'); - }); - - it('changes direction when picking an ltr lang', async function () { - const chrome$ = helper.padChrome$; - - // click on the settings button to make settings visible - const $settingsButton = chrome$('.buttonicon-settings'); - $settingsButton.trigger('click'); - - // click the language button - const $language = chrome$('#languagemenu'); - const $languageoption = $language.find('[value=en]'); - - // select english - // select arabic - $languageoption.attr('selected', 'selected'); - $language.val('en'); - $languageoption.trigger('change'); - - await helper.waitForPromise(() => chrome$('html')[0].dir !== 'rtl'); - - // check if the document's direction was changed - expect(chrome$('html')[0].dir).to.be('ltr'); - }); -}); diff --git a/src/tests/frontend/specs/timeslider.js b/src/tests/frontend/specs/timeslider.js deleted file mode 100644 index 6ee00f108..000000000 --- a/src/tests/frontend/specs/timeslider.js +++ /dev/null @@ -1,41 +0,0 @@ -'use strict'; - -// deactivated, we need a nice way to get the timeslider, this is ugly -xdescribe('timeslider button takes you to the timeslider of a pad', function () { - beforeEach(async function () { - await helper.aNewPad(); - }); - - it('timeslider contained in URL', async function () { - const inner$ = helper.padInner$; - const chrome$ = helper.padChrome$; - - // get the first text element inside the editable space - const $firstTextElement = inner$('div span').first(); - const originalValue = $firstTextElement.text(); // get the original value - $firstTextElement.sendkeys('Testing'); // send line 1 to the pad - - const modifiedValue = $firstTextElement.text(); // get the modified value - expect(modifiedValue).not.to.be(originalValue); // expect the value to change - - // The value has changed so we can.. - await helper.waitForPromise(() => modifiedValue !== originalValue); - - const $timesliderButton = chrome$('#timesliderlink'); - $timesliderButton.trigger('click'); // So click the timeslider link - - await helper.waitForPromise(() => { - const iFrameURL = chrome$.window.location.href; - if (iFrameURL) { - return iFrameURL.indexOf('timeslider') !== -1; - } else { - return false; // the URL hasnt been set yet - } - }); - - // click the buttons - const iFrameURL = chrome$.window.location.href; // get the url - const inTimeslider = iFrameURL.indexOf('timeslider') !== -1; - expect(inTimeslider).to.be(true); // expect the value to change - }); -}); diff --git a/src/tests/frontend/specs/timeslider_follow.js b/src/tests/frontend/specs/timeslider_follow.js deleted file mode 100644 index e3d9b8067..000000000 --- a/src/tests/frontend/specs/timeslider_follow.js +++ /dev/null @@ -1,101 +0,0 @@ -'use strict'; - -describe('timeslider follow', function () { - // create a new pad before each test run - beforeEach(async function () { - await helper.aNewPad(); - }); - - // TODO needs test if content is also followed, when user a makes edits - // while user b is in the timeslider - it("content as it's added to timeslider", async function () { - this.timeout(20000); - // send 6 revisions - const revs = 6; - const message = 'a\n\n\n\n\n\n\n\n\n\n'; - const newLines = message.split('\n').length; - for (let i = 0; i < revs; i++) { - await helper.edit(message, newLines * i + 1); - } - - await helper.gotoTimeslider(0); - await helper.waitForPromise(() => helper.contentWindow().location.hash === '#0'); - - const originalTop = helper.contentWindow().$('#innerdocbody').offset(); - - // set to follow contents as it arrives - helper.contentWindow().$('#options-followContents').prop('checked', true); - helper.contentWindow().$('#playpause_button_icon').trigger('click'); - - let newTop; - await helper.waitForPromise(() => { - newTop = helper.contentWindow().$('#innerdocbody').offset(); - return newTop.top < originalTop.top; - }); - }); - - /** - * Tests for bug described in #4389 - * The goal is to scroll to the first line that contains a change right before - * the change is applied. - */ - it('only to lines that exist in the pad view, regression test for #4389', async function () { - await helper.clearPad(); - await helper.edit('Test line\n' + - '\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n' + - '\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n' + - '\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n'); - await helper.edit('Another test line', 40); - - - await helper.gotoTimeslider(); - - // set to follow contents as it arrives - helper.contentWindow().$('#options-followContents').prop('checked', true); - - const oldYPosition = helper.contentWindow().$('#editorcontainerbox')[0].scrollTop; - expect(oldYPosition).to.be(0); - - /** - * pad content rev 0 [default Pad text] - * pad content rev 1 [''] - * pad content rev 2 ['Test line','','', ..., ''] - * pad content rev 3 ['Test line','',..., 'Another test line', ..., ''] - */ - - // line 40 changed - helper.contentWindow().$('#leftstep').trigger('click'); - await helper.waitForPromise(() => hasFollowedToLine(40)); - - // line 1 is the first line that changed - helper.contentWindow().$('#leftstep').trigger('click'); - await helper.waitForPromise(() => hasFollowedToLine(1)); - - // line 1 changed - helper.contentWindow().$('#leftstep').trigger('click'); - await helper.waitForPromise(() => hasFollowedToLine(1)); - - // line 1 changed - helper.contentWindow().$('#rightstep').trigger('click'); - await helper.waitForPromise(() => hasFollowedToLine(1)); - - // line 1 is the first line that changed - helper.contentWindow().$('#rightstep').trigger('click'); - await helper.waitForPromise(() => hasFollowedToLine(1)); - - // line 40 changed - helper.contentWindow().$('#rightstep').trigger('click'); - helper.waitForPromise(() => hasFollowedToLine(40)); - }); -}); - -/** - * @param {number} lineNum - * @returns {boolean} scrolled to the lineOffset? - */ -const hasFollowedToLine = (lineNum) => { - const scrollPosition = helper.contentWindow().$('#editorcontainerbox')[0].scrollTop; - const lineOffset = - helper.contentWindow().$('#innerdocbody').find(`div:nth-child(${lineNum})`)[0].offsetTop; - return Math.abs(scrollPosition - lineOffset) < 1; -};