Add cypress test for highlights

This commit is contained in:
Ramon Wenger 2024-02-07 11:52:37 +01:00
parent 91dcf3cb20
commit 8afe36e8ea
11 changed files with 182 additions and 24 deletions

View File

@ -0,0 +1,136 @@
import { defaultModuleQueriesandMutations } from '../../../support/helpers';
const contentBlockId = window.btoa('ContentBlockNode:1');
const operations = {
...defaultModuleQueriesandMutations,
ModuleDetailsQuery: {
module: {
chapters: [
{
title: 'A Chapter',
contentBlocks: [
{
title: 'A Content Block',
highlights: [],
id: contentBlockId,
contents: [
{
type: 'text_block',
id: 'some-content-component-id',
value: {
text: '<p>Dies ist ein Text mit ein paar Wörtern.</p>',
},
},
],
},
],
},
],
},
},
AddHighlight: ({ input: { highlight } }) => {
return {
addHighlight: {
highlight: {
...highlight,
id: 'new-highlight-id',
contentBlock: {
id: contentBlockId,
},
},
},
};
},
DeleteHighlight: {
deleteHighlight: {
success: true,
},
},
};
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));
};
describe('Highlights', () => {
beforeEach(() => {
cy.setup();
cy.mockGraphqlOps({
operations,
});
});
it('visits a module and highlights a paragraph', () => {
cy.visit('/module/my-module');
/*
* Mark the text (programmatically, as Cypress has no API for this)
*/
cy.getByDataCy('text-block').then(($elm) => {
const textBlock = $elm[0];
const paragraph = textBlock.querySelector('p');
selectText(paragraph.childNodes[0], 2, 12);
});
/*
* Also trigger the events manually
*/
cy.document().trigger('selectionchange');
cy.getByDataCy('content-block-div').trigger('mouseup');
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
*/
cy.window().then((win) => {
const selection = win.getSelection();
selection.removeAllRanges();
});
// mark the text with yellow and check the text
cy.getByDataCy('highlight-alpha').click();
cy.getByDataCy('highlight-mark').should('contain', 'es ist ein');
// 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);
// 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', 'es ist ein');
cy.getByDataCy('highlight-popover').should('have.length', 1); // there should only be one popover
cy.getByDataCy('highlight-mark').should('have.length', 1);
// 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');
// todo: change the color
// todo: write a note
// todo: click the note icon without first setting a color
// delete the highlight
cy.getByDataCy('highlight-delete').click();
cy.getByDataCy('confirm-dialog').should('be.visible');
cy.getByDataCy('modal-save-button').click();
cy.getByDataCy('highlight-popover').should('not.exist');
cy.getByDataCy('highlight-sidebar').should('not.exist');
cy.getByDataCy('highlight-mark').should('not.exist');
});
});

View File

@ -2,7 +2,12 @@
"compilerOptions": { "compilerOptions": {
"target": "es5", "target": "es5",
"lib": ["es5", "dom"], "lib": ["es5", "dom"],
"types": ["cypress", "node"] "types": ["cypress", "node"],
"baseUrl": "./../src/",
"paths": {
"@/*": ["./*"],
"cypress/*": ["./cypress/*"]
}
}, },
"include": ["**/*.ts"] "include": ["**/*.ts"]
} }

View File

@ -32,6 +32,7 @@
"@tiptap/extension-text": "^2.1.11", "@tiptap/extension-text": "^2.1.11",
"@tiptap/vue-3": "^2.1.11", "@tiptap/vue-3": "^2.1.11",
"@types/mark.js": "^8.11.12", "@types/mark.js": "^8.11.12",
"@types/mocha": "^10.0.6",
"@types/rangy": "^0.0.38", "@types/rangy": "^0.0.38",
"@typescript-eslint/eslint-plugin": "^5.10.0", "@typescript-eslint/eslint-plugin": "^5.10.0",
"@typescript-eslint/parser": "^5.10.0", "@typescript-eslint/parser": "^5.10.0",
@ -6177,6 +6178,11 @@
"@types/jquery": "*" "@types/jquery": "*"
} }
}, },
"node_modules/@types/mocha": {
"version": "10.0.6",
"resolved": "https://registry.npmjs.org/@types/mocha/-/mocha-10.0.6.tgz",
"integrity": "sha512-dJvrYWxP/UcXm36Qn36fxhUKu8A/xMRXVT2cliFF1Z7UA9liG5Psj3ezNSZw+5puH2czDXRLcXQxf8JbJt0ejg=="
},
"node_modules/@types/node": { "node_modules/@types/node": {
"version": "18.11.5", "version": "18.11.5",
"resolved": "https://registry.npmjs.org/@types/node/-/node-18.11.5.tgz", "resolved": "https://registry.npmjs.org/@types/node/-/node-18.11.5.tgz",
@ -22599,6 +22605,11 @@
"@types/jquery": "*" "@types/jquery": "*"
} }
}, },
"@types/mocha": {
"version": "10.0.6",
"resolved": "https://registry.npmjs.org/@types/mocha/-/mocha-10.0.6.tgz",
"integrity": "sha512-dJvrYWxP/UcXm36Qn36fxhUKu8A/xMRXVT2cliFF1Z7UA9liG5Psj3ezNSZw+5puH2czDXRLcXQxf8JbJt0ejg=="
},
"@types/node": { "@types/node": {
"version": "18.11.5", "version": "18.11.5",
"resolved": "https://registry.npmjs.org/@types/node/-/node-18.11.5.tgz", "resolved": "https://registry.npmjs.org/@types/node/-/node-18.11.5.tgz",

View File

@ -59,6 +59,7 @@
"@tiptap/extension-text": "^2.1.11", "@tiptap/extension-text": "^2.1.11",
"@tiptap/vue-3": "^2.1.11", "@tiptap/vue-3": "^2.1.11",
"@types/mark.js": "^8.11.12", "@types/mark.js": "^8.11.12",
"@types/mocha": "^10.0.6",
"@types/rangy": "^0.0.38", "@types/rangy": "^0.0.38",
"@typescript-eslint/eslint-plugin": "^5.10.0", "@typescript-eslint/eslint-plugin": "^5.10.0",
"@typescript-eslint/parser": "^5.10.0", "@typescript-eslint/parser": "^5.10.0",

View File

@ -170,13 +170,17 @@ const childElements = computed(() => {
return []; return [];
}); });
const unmark = () => {
for (const paragraph of childElements.value) {
const instance = new Mark(paragraph);
instance.unmark();
}
};
watch( watch(
() => filteredHighlights.value.map((h) => h.color), () => filteredHighlights.value.map((h) => h.color),
() => { () => {
for (const paragraph of childElements.value) { unmark();
const instance = new Mark(paragraph);
instance.unmark();
}
markHighlights(); markHighlights();
} }
); );
@ -242,6 +246,7 @@ const markHighlights = () => {
className: `highlight highlight--${highlight.color}`, className: `highlight highlight--${highlight.color}`,
each: (newMark: HTMLElement) => { each: (newMark: HTMLElement) => {
newMark.dataset.id = highlight.id; newMark.dataset.id = highlight.id;
newMark.dataset.cy = 'highlight-mark';
newMark.addEventListener('click', () => { newMark.addEventListener('click', () => {
if (contentComponentDiv.value) { if (contentComponentDiv.value) {
popover.show({ popover.show({
@ -340,8 +345,10 @@ const markHighlights = () => {
}; };
onMounted(() => { onMounted(() => {
console.log('onMounted ContentComponent called'); setTimeout(() => {
setTimeout(markHighlights, 500); unmark();
markHighlights();
}, 1);
}); });
</script> </script>

View File

@ -1,8 +1,12 @@
<template> <template>
<div class="highlight-popover"> <div
data-cy="highlight-popover"
class="highlight-popover"
>
<section class="highlight-popover__section"> <section class="highlight-popover__section">
<div <div
class="highlight-popover__color highlight-popover__color--yellow" class="highlight-popover__color highlight-popover__color--yellow"
data-cy="highlight-alpha"
@click="$emit('choose-color', 'alpha')" @click="$emit('choose-color', 'alpha')"
></div> ></div>
<div <div
@ -17,12 +21,14 @@
<section class="highlight-popover__section"> <section class="highlight-popover__section">
<note-icon <note-icon
class="highlight-popover__icon" class="highlight-popover__icon"
data-cy="highlight-note"
@click="$emit('note')" @click="$emit('note')"
/> />
</section> </section>
<section class="highlight-popover__section"> <section class="highlight-popover__section">
<trash-icon <trash-icon
class="highlight-popover__icon" class="highlight-popover__icon"
data-cy="highlight-delete"
@click="confirmDelete" @click="confirmDelete"
/> />
</section> </section>
@ -37,10 +43,9 @@ export interface Props {
top: string; top: string;
} }
const emit = defineEmits(['close', 'confirm', 'choose-color']); const emit = defineEmits(['close', 'confirm', 'choose-color', 'delete']);
defineProps<Props>(); defineProps<Props>();
const confirmDelete = () => { const confirmDelete = () => {
console.log('trying to delete');
modal modal
.show() .show()
.then(() => { .then(() => {

View File

@ -1,5 +1,8 @@
<template> <template>
<div class="highlight-sidebar"> <div
data-cy="highlight-sidebar"
class="highlight-sidebar"
>
<div <div
class="highlight-sidebar__close-button" class="highlight-sidebar__close-button"
@click="close" @click="close"
@ -11,6 +14,7 @@
<mark <mark
class="paragraph highlight-sidebar__highlighted-text" class="paragraph highlight-sidebar__highlighted-text"
:class="[`highlight--${highlight.color}`]" :class="[`highlight--${highlight.color}`]"
data-cy="highlight-in-sidebar"
ref="highlightMark" ref="highlightMark"
> >
{{ highlight.text }} {{ highlight.text }}

View File

@ -1,6 +1,7 @@
<template> <template>
<modal <modal
class="confirm-dialog" class="confirm-dialog"
data-cy="confirm-dialog"
:small="true" :small="true"
> >
<template #header> <template #header>

View File

@ -173,7 +173,6 @@ export const getSelectionHandler =
const positionOfSelection = startAncestor?.getBoundingClientRect(); const positionOfSelection = startAncestor?.getBoundingClientRect();
const offsetTop = positionOfSelection.top - positionOfContentBlock.top; const offsetTop = positionOfSelection.top - positionOfContentBlock.top;
const onChooseColor = (color: string) => { const onChooseColor = (color: string) => {
console.log('chosenColor', color);
highlightedText.color = color; highlightedText.color = color;
if (onChangeColor) { if (onChangeColor) {
onChangeColor(highlightedText); onChangeColor(highlightedText);

View File

@ -16,20 +16,12 @@ export default {
const mountEl = document.createElement('div'); const mountEl = document.createElement('div');
wrapper.appendChild(mountEl); wrapper.appendChild(mountEl);
let _resolve: ResolveReject, _reject: ResolveReject;
const promise = new Promise((resolve, reject) => {
_resolve = resolve;
_reject = reject;
});
const clickOutsideElementListener = (event: Event) => { const clickOutsideElementListener = (event: Event) => {
// inspired by https://stackoverflow.com/a/3028037/6071058 // inspired by https://stackoverflow.com/a/3028037/6071058
const isInsidePopover = mountEl.contains(event.target as Node); // the click happened inside the Popover const isInsidePopover = mountEl.contains(event.target as Node); // the click happened inside the Popover
// const isInsideWrapper = wrapper.contains(event.target as Node); // the click happened inside the wrapper // const isInsideWrapper = wrapper.contains(event.target as Node); // the click happened inside the wrapper
const isInsideSidebar = document.querySelector('.highlight-sidebar')?.contains(event.target as Node); // the click happened inside the HighlightSidebar const isInsideSidebar = document.querySelector('.highlight-sidebar')?.contains(event.target as Node); // the click happened inside the HighlightSidebar
if (!(isInsidePopover || isInsideSidebar)) { if (!(isInsidePopover || isInsideSidebar)) {
_reject();
cleanUp(); cleanUp();
} }
}; };
@ -44,7 +36,6 @@ export default {
top: `${Math.floor(offsetTop)}px`, top: `${Math.floor(offsetTop)}px`,
onConfirm() { onConfirm() {
cleanUp(); cleanUp();
_resolve();
}, },
onNote() { onNote() {
if (onNote) { if (onNote) {
@ -53,7 +44,6 @@ export default {
}, },
onClose() { onClose() {
cleanUp(); cleanUp();
_reject();
}, },
onDelete() { onDelete() {
if (onDelete) { if (onDelete) {
@ -69,6 +59,5 @@ export default {
setTimeout(() => { setTimeout(() => {
document.addEventListener('click', clickOutsideElementListener); document.addEventListener('click', clickOutsideElementListener);
}, 1); // do not trigger on the same event that created the popover }, 1); // do not trigger on the same event that created the popover
return promise;
}, },
}; };

View File

@ -4,5 +4,5 @@
"target": "es5", "target": "es5",
"types": ["cyress", "node"] "types": ["cyress", "node"]
}, },
"include": ["src/**/*.cy.js"] "include": ["src/**/*.cy.js", "src/**/*.cy.ts"]
} }