Add cypress test for highlights
This commit is contained in:
parent
91dcf3cb20
commit
8afe36e8ea
|
|
@ -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');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
@ -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"]
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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",
|
||||||
|
|
|
||||||
|
|
@ -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",
|
||||||
|
|
|
||||||
|
|
@ -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>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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(() => {
|
||||||
|
|
|
||||||
|
|
@ -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 }}
|
||||||
|
|
|
||||||
|
|
@ -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>
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
|
|
|
||||||
|
|
@ -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;
|
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -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"]
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue