skillbox/client/cypress/e2e/frontend/modules/highlights.cy.ts

644 lines
20 KiB
TypeScript
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import log from 'loglevel';
import { defaultModuleQueriesandMutations } from '../../../support/helpers';
import { instrumentWithLongContent } from './instrument-highlights';
const contentBlockId = window.btoa('ContentBlockNode:1');
const instrumentId = window.btoa('InstrumentNode:2');
const instrumentSlug = 'my-instrument';
const chapterId = window.btoa('ChapterNode:3');
const moduleId = window.btoa('ModuleNode:3');
const moduleSlug = 'my-module';
const getModule = (contents) => {
return {
intro: '<p>Introducing this great paragraph!</p>',
highlights: [],
slug: moduleSlug,
chapters: [
{
title: 'A Chapter',
description: 'This is something else',
highlights: [],
id: chapterId,
contentBlocks: [
{
title: 'A Content Block',
highlights: [],
id: contentBlockId,
contents,
},
],
},
],
};
};
const defaultContents = [
{
type: 'text_block',
id: 'some-content-component-id',
value: {
text: '<p>Dies ist ein Text mit ein paar Wörtern.</p>',
},
},
];
const contentListContents = [
{
id: '16ea6f8a-99bc-4366-9f32-d7c3e7e39765',
type: 'content_list_item',
value: [
{
id: '73f571e9-c049-41e7-8927-c4badbfb9b93',
type: 'text_block',
value: {
text: '<p data-block-key="qykuf">Hallo Hallo Hallo Hallo Hallo Hallo Hallo Hallo Hallo Hallo Hallo Hallo Hallo Hallo Hallo Hallo Hallo Hallo Hallo Hallo Hallo Hallo Hallo Hallo Hallo Hallo Hallo Hallo Hallo Hallo Hallo Hallo Hallo Hallo Hallo Hallo Hallo Hallo Hallo<br/><br/> Hallo Hallo Hallo Hallo Hallo Hallo Hallo Hallo Hallo Hallo Hallo Hallo Hallo Hallo Hallo Hallo Hallo Hallo Hallo Hallo Hallo Hallo Hallo Hallo Hallo Hallo Hallo Hallo Hallo Hallo Hallo Hallo Hallo Hallo Hallo Hallo Hallo Hallo Hallo</p>',
},
},
],
},
];
const subTitleContents = [
{
id: 'c4eaf571-de75-436b-b4d7-9178f2a86979',
type: 'subtitle',
value: {
text: 'Wozu dienen Visualisierungen?',
},
},
];
const getAddHighlight = (pageType: string = 'ModuleNode') => {
log.debug(`adding highlight for page ${pageType}`);
let page: { id: string; __typename: string; slug?: string };
if (pageType === 'ChapterNode') {
page = {
id: chapterId,
__typename: 'ChapterNode',
};
} else if (pageType === 'InstrumentNode') {
page = {
slug: instrumentSlug,
id: instrumentId,
__typename: 'InstrumentNode',
};
} else {
page = {
id: moduleId,
slug: moduleSlug,
__typename: 'ModuleNode',
};
}
return ({ input: { highlight } }) => {
lastHighlight = {
...highlight,
id: 'new-highlight-id',
page,
};
return {
addHighlight: {
highlight: lastHighlight,
},
};
};
};
const getAddContentHighlight = (pageType: string = 'ContentBlockNode') => {
log.debug(`adding content highlight for page type ${pageType}`);
let page: { id: string; __typename: string; slug?: string };
if (pageType === 'InstrumentNode') {
page = {
id: instrumentId,
slug: instrumentSlug,
__typename: 'InstrumentNode',
};
} else {
page = {
id: contentBlockId,
__typename: 'ContentBlockNode',
};
}
console.log(page);
return ({ input: { highlight } }) => {
lastHighlight = {
...highlight,
id: 'new-highlight-id',
page,
};
return {
addContentHighlight: {
highlight: lastHighlight,
},
};
};
};
let lastHighlight;
const defaultModule = getModule(defaultContents);
const getOperations = (pageType = 'ModuleNode') => ({
...defaultModuleQueriesandMutations,
ModuleDetailsQuery: {
module: defaultModule,
},
UpdateLastModule: {
updateLastModule: {
lastModule: defaultModule,
},
},
AddHighlight: getAddHighlight(pageType),
AddContentHighlight: getAddContentHighlight(pageType),
UpdateHighlight: ({ input: { note, color } }) => {
lastHighlight = {
...lastHighlight,
note,
color,
};
return {
updateHighlight: {
highlight: lastHighlight,
},
};
},
DeleteHighlight: {
deleteHighlight: {
success: true,
},
},
});
const operations = getOperations();
const selectText = (elm: Node, start: number, end: number) => {
const range = document.createRange();
range.setStart(elm, start);
range.setEnd(elm, end);
cy.window().then((win) => win.getSelection().addRange(range));
};
const makeSelection = (dataCy, tag = null) => {
cy.getByDataCy(dataCy).then(($elm) => {
const textBlock = $elm[0];
let paragraph;
if (tag) {
paragraph = textBlock.querySelector(tag);
} else {
paragraph = textBlock;
}
selectText(paragraph.childNodes[0], 2, 12);
/*
* Also trigger the events manually
*/
cy.document().trigger('selectionchange');
cy.wrap(paragraph.parentNode).trigger('mouseup');
});
};
const removeSelection = () => {
cy.window().then((win) => {
const selection = win.getSelection();
selection.removeAllRanges();
});
};
const markText = (dataCy = 'text-block', tag = 'p') => {
/*
* Mark the text (programmatically, as Cypress has no API for this)
*/
makeSelection(dataCy, tag);
cy.getByDataCy('highlight-popover').should('be.visible');
cy.getByDataCy('highlight-popover').should('have.length', 1); // there should only be one popover
/*
* manually remove the selection, because cypress does not do that on a click, unlike a real browser
*/
removeSelection();
};
const createHighlight = (text: string, content = false, numHighlights = 1) => {
// delete doesn't make sense before the highlight exists
cy.getByDataCy('highlight-delete').should('not.exist');
// mark the text with yellow and check the text
cy.getByDataCy('highlight-alpha').click();
if (content) {
cy.wait('@AddContentHighlight');
} else {
cy.wait('@AddHighlight');
}
cy.getByDataCy('highlight-mark').should('contain', text);
// we only want to have one of each element and not accidentally create multiple
cy.getByDataCy('highlight-popover').should('have.length', 1); // there should only be one popover
cy.getByDataCy('highlight-mark').should('have.length', numHighlights);
};
const updateHighlight = (text, color = 'beta') => {
cy.getByDataCy(`highlight-${color}`).click();
cy.wait('@UpdateHighlight');
cy.getByDataCy('highlight-mark').should('contain', text);
cy.getByDataCy('highlight-mark').should('have.class', `highlight--${color}`);
//
// we only want to have one of each element and not accidentally create multiple
cy.getByDataCy('highlight-popover').should('have.length', 1); // there should only be one popover
cy.getByDataCy('highlight-mark').should('have.length', 1);
};
const openSidebar = (text) => {
// display the sidebar and popover and check them
cy.getByDataCy('highlight-note').click();
cy.getByDataCy('highlight-popover').should('be.visible');
cy.getByDataCy('highlight-sidebar').should('be.visible');
cy.getByDataCy('highlight-in-sidebar').should('contain', text);
cy.getByDataCy('highlight-popover').should('have.length', 1); // there should only be one popover
cy.getByDataCy('highlight-mark').should('have.length', 1);
};
const deleteHighlight = () => {
// delete the highlight
cy.getByDataCy('highlight-mark').click();
cy.getByDataCy('highlight-delete').click();
cy.getByDataCy('confirm-dialog').should('be.visible');
cy.getByDataCy('modal-save-button').click();
cy.wait('@DeleteHighlight');
cy.getByDataCy('highlight-popover').should('not.exist');
cy.getByDataCy('highlight-sidebar').should('not.exist');
cy.getByDataCy('highlight-mark').should('not.exist');
};
const addNote = () => {
const textPart = 'Some noteworthy stuff with a link to ';
const urlPart = 'https://hep.ch';
const note = `${textPart}${urlPart}`;
cy.getByDataCy('highlight-note-input').should('have.value', '').type(note);
cy.getByDataCy('highlight-note-save').click();
cy.wait('@UpdateHighlight');
cy.getByDataCy('highlight-note-save').should('not.exist');
cy.getByDataCy('highlight-note-text').should('contain.text', textPart);
cy.get(`[href="${urlPart}"]`).should('exist');
};
describe('Highlights', () => {
beforeEach(() => {
cy.setup();
});
it('visits a module and highlights a paragraph', () => {
cy.mockGraphqlOps({
operations,
});
cy.visit('/module/my-module');
markText();
const highlightedText = 'es ist ein';
createHighlight(highlightedText, true);
updateHighlight(highlightedText);
openSidebar(highlightedText);
// click outside the created components to make them disappear
cy.getByDataCy('module-title').click();
cy.getByDataCy('highlight-popover').should('not.exist');
cy.getByDataCy('highlight-sidebar').should('not.exist');
cy.getByDataCy('highlight-mark').should('have.length', 1);
// click on the highlighted text to make the menus appear again
cy.getByDataCy('highlight-mark').click();
cy.getByDataCy('highlight-popover').should('be.visible');
cy.getByDataCy('highlight-sidebar').should('be.visible');
updateHighlight(highlightedText, 'gamma');
cy.getByDataCy('highlight-in-sidebar').should('have.class', 'highlight--gamma');
// todo: write a note
// todo: click the note icon without first setting a color
deleteHighlight();
});
it('visits a module with a ContentListItem and highlights some text', () => {
cy.mockGraphqlOps({
operations: {
...operations,
ModuleDetailsQuery: {
module: getModule(contentListContents),
},
},
});
cy.visit('/module/my-module');
markText();
});
it('visits a module and highlights some text, then adds a note', () => {
cy.mockGraphqlOps({
operations: {
...operations,
ModuleDetailsQuery: {
module: getModule(defaultContents),
},
},
});
cy.visit('/module/my-module');
markText();
cy.getByDataCy('highlight-note').click();
cy.wait('@AddContentHighlight');
addNote();
});
it('visits a module and highlights some text, clicks on the Note icon, then changes the color', () => {
cy.mockGraphqlOps({
operations,
});
cy.visit('/module/my-module');
markText();
const highlightedText = 'es ist ein';
// delete doesn't make sense before the highlight exists
cy.getByDataCy('highlight-delete').should('not.exist');
openSidebar(highlightedText);
cy.wait('@AddContentHighlight');
updateHighlight(highlightedText);
cy.getByDataCy('highlight-mark').should('have.length', 1);
});
it('visits a module and highlights the chapter description', () => {
cy.mockGraphqlOps({
operations: getOperations('ChapterNode'),
});
cy.visit('/module/my-module');
markText('chapter-intro');
const highlightedText = 'is is some';
// delete doesn't make sense before the highlight exists
cy.getByDataCy('highlight-delete').should('not.exist');
openSidebar(highlightedText);
cy.wait('@AddHighlight');
updateHighlight(highlightedText);
cy.getByDataCy('highlight-mark').should('have.length', 1);
deleteHighlight();
});
it('visits a module and highlights the module description', () => {
cy.mockGraphqlOps({
operations,
});
cy.visit('/module/my-module');
markText('module-intro');
const highlightedText = 'troducing';
// delete doesn't make sense before the highlight exists
cy.getByDataCy('highlight-delete').should('not.exist');
openSidebar(highlightedText);
cy.wait('@AddHighlight');
updateHighlight(highlightedText);
cy.getByDataCy('highlight-mark').should('have.length', 1);
deleteHighlight();
});
it('visits an instrument and highlights some text', () => {
cy.mockGraphqlOps({
operations: {
...operations,
AddContentHighlight: getAddContentHighlight('InstrumentNode'),
InstrumentQuery: {
instrument: {
title: 'My Instrument',
id: instrumentId,
slug: instrumentSlug,
highlights: [],
contents: [
{
type: 'text_block',
id: 'some-other-content-component-id',
value: {
text: '<p>Hello my beautiful World!</p>',
},
},
],
},
},
},
});
cy.visit(`/instrument/${instrumentSlug}`);
cy.wait('@InstrumentQuery');
markText();
const highlightedText = 'llo my bea';
createHighlight(highlightedText, true);
updateHighlight(highlightedText);
addNote();
deleteHighlight();
});
it('visits an instrument and writes a note', () => {
cy.mockGraphqlOps({
operations: {
...operations,
AddContentHighlight: getAddContentHighlight('InstrumentNode'),
InstrumentQuery: {
instrument: {
title: 'My Instrument',
id: instrumentId,
slug: instrumentSlug,
highlights: [],
contents: [
{
type: 'text_block',
id: 'some-other-content-component-id',
value: {
text: '<p>Hello my beautiful World!</p>',
},
},
],
},
},
},
});
cy.visit(`/instrument/${instrumentSlug}`);
cy.wait('@InstrumentQuery');
markText();
const highlightedText = 'llo my bea';
openSidebar(highlightedText);
cy.wait('@AddContentHighlight');
updateHighlight(highlightedText);
deleteHighlight();
});
it('visits an instrument with long content and highlights some text', () => {
cy.mockGraphqlOps({
operations: {
...operations,
AddContentHighlight: getAddContentHighlight('InstrumentNode'),
InstrumentQuery: {
instrument: instrumentWithLongContent,
},
},
});
cy.visit(`/instrument/${instrumentSlug}`);
cy.wait('@InstrumentQuery');
markText();
const highlightedText = 'nn eine Pe';
createHighlight(highlightedText, true);
updateHighlight(highlightedText);
addNote();
deleteHighlight();
});
it('visits an instrument and highlights the intro', () => {
const intro = '<p>Something here in the intro, right?</p>';
cy.mockGraphqlOps({
operations: {
...operations,
AddHighlight: getAddHighlight('InstrumentNode'),
InstrumentQuery: {
instrument: {
title: 'My Instrument',
id: instrumentId,
slug: instrumentSlug,
intro,
highlights: [],
contents: [
{
type: 'text_block',
id: 'some-other-content-component-id',
value: {
text: '<p>Hello my beautiful World!</p>',
},
},
],
},
},
},
});
cy.visit(`/instrument/${instrumentSlug}`);
cy.wait('@InstrumentQuery');
cy.getByDataCy('instrument-intro').should('have.html', intro);
markText('instrument-intro');
const highlightedText = 'mething he';
createHighlight(highlightedText, false);
updateHighlight(highlightedText);
addNote();
deleteHighlight();
});
it('visits an instrument with a subtitle and does not display the popover', () => {
cy.mockGraphqlOps({
operations: {
...operations,
InstrumentQuery: {
instrument: {
title: 'some instrument',
id: instrumentId,
slug: instrumentSlug,
contents: subTitleContents,
},
},
},
});
cy.visit(`/instrument/${instrumentSlug}`);
cy.wait('@InstrumentQuery');
// markText('subtitle-block');
cy.getByDataCy('subtitle-block').should('have.text', 'Wozu dienen Visualisierungen?');
makeSelection('subtitle-block');
cy.getByDataCy('highlight-popover').should('not.exist');
});
describe.skip('with paragraphs and lists', () => {
beforeEach(() => {
const contents = [
{
id: 'eaf8016b-ba47-4690-8e3b-21296cc095fa',
type: 'assignment',
value: {
title: 'A LF1 A8: Ziele',
assignment:
'<p data-block-key="hed7c">Ihr Produkt und Ihre Erkenntnisse aus dem folgenden Lernauftrag können Sie in Ihr persönliches Portfolio aufnehmen.</p><p data-block-key="d739u">Formulieren Sie je drei bis fünf persönliche Ziele …</p><ul><li data-block-key="9ske7">für den schulischen Bereich.</li><li data-block-key="71vi0">für den Bereich des Lehrbetriebs.</li></ul><p data-block-key="849i"></p><p data-block-key="6qgb7">Wenden Sie dabei Ihre Kenntnisse aus dem Studium des Sachtextes an. Tauschen Sie sich in Gruppen über Ihre Ziele aus. Wer hat gleiche oder ähnliche Ziele? Wo gibt es Unterschiede? Was möchte ich von wem noch wissen?</p>',
id: 'QXNzaWdubWVudE5vZGU6MzU=',
},
},
];
// const contents = [
// {
// type: 'text_block',
// id: 'some-other-content-component-id',
// value: {
// text: `
// <p>First, there is a paragraph</p>
// <ul>
// <li>Then, there is a list</li>
// <li>With multiple list items</li>
// </ul>
// <p>Then, there is another paragrah</p>
// `,
// },
// },
// ];
cy.mockGraphqlOps({
operations: {
...operations,
AddHighlight: getAddHighlight('InstrumentNode'),
ModuleDetailsQuery: {
module: getModule(contents),
},
AssignmentQuery: {
assignment: {
id: 'QXNzaWdubWVudE5vZGU6Mzk=',
title: 'A LF1 A13: Ziele und Vorhaben',
assignment:
'<p data-block-key="6fgwz">Ihr Produkt und Ihre Erkenntnisse aus dem folgenden Lernauftrag können Sie in Ihr persönliches Portfolio aufnehmen.</p><p data-block-key="1l7np">Notieren Sie Ihre Ziele und Vorhaben für Ihre berufliche und private Zukunft. Gestalten Sie anschliessend ein Vision Board als Plakat oder in digitaler Form.</p><p data-block-key="a0fqk">Zusätzliche Möglichkeit: Sie wählen Ziele und Vorhaben für ein gesellschaftliches Thema wie zum Beispiel Zusammenleben, Weltfrieden, Naturschutz usw.</p><p data-block-key="6kj6a">Hinweis: Im Internet finden Sie zahlreiche Anleitungen zum Erstellen eines Vision Boards. Lassen Sie sich dadurch inspirieren, doch verwenden Sie keine Vorlagen. Vertrauen Sie auf Ihre eigene Kreativität und kreieren Sie Ihr ganz persönliches Vision Board.</p>',
solution: null,
},
},
},
});
cy.visit(`/module/${moduleSlug}`);
cy.wait('@ModuleDetailsQuery');
cy.wait('@AssignmentQuery');
});
it('highlights a paragraph', () => {
markText('assignment-main-text');
const highlightedText = 'rst, there';
createHighlight(highlightedText, true);
updateHighlight(highlightedText);
addNote();
deleteHighlight();
});
it('highlights a list item', () => {
markText('assignment-main-text', 'li');
const highlightedText = 'en, there';
createHighlight(highlightedText, true);
updateHighlight(highlightedText);
addNote();
deleteHighlight();
});
it('highlights a paragraph and a list item', () => {
const highlightedText = 'rst, there';
markText('assignment-main-text', 'p');
createHighlight(highlightedText, true);
// click outside the created components to make them disappear
cy.getByDataCy('module-title').click();
const secondHighlightedText = 'en, there';
markText('text-block', 'li');
createHighlight(secondHighlightedText, true, 2);
updateHighlight(secondHighlightedText);
addNote();
deleteHighlight();
});
});
});