Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
228 changes: 228 additions & 0 deletions webapp/cypress/component/TiptapInlineCompatibilityTest.cy.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,228 @@
import TiptapInline from "@/components/TiptapInline.vue";
import { createStore } from "vuex";

describe("TiptapInline - TinyMCE HTML Compatibility", () => {
let store;

beforeEach(() => {
store = createStore({
state: {},
mutations: {},
getters: {},
});
});

it("renders basic formatting from TinyMCE HTML", () => {
const tinyMCEHtml = `<p><strong>Bold text</strong> and <em>italic text</em> and <u>underlined text</u> and <s>strikethrough</s></p>`;

cy.mount(TiptapInline, {
props: {
modelValue: tinyMCEHtml,
},
global: {
plugins: [store],
},
});

cy.get(".ProseMirror").should("contain.html", "<strong>Bold text</strong>");
cy.get(".ProseMirror").should("contain.html", "<em>italic text</em>");
cy.get(".ProseMirror").should("contain.html", "<u>underlined text</u>");
cy.get(".ProseMirror").should("contain.html", "<s>strikethrough</s>");
});

it("renders headings from TinyMCE HTML", () => {
const tinyMCEHtml = `<h1>Heading 1</h1><h2>Heading 2</h2><h3>Heading 3</h3>`;

cy.mount(TiptapInline, {
props: {
modelValue: tinyMCEHtml,
},
global: {
plugins: [store],
},
});

cy.get(".ProseMirror h1").should("contain.text", "Heading 1");
cy.get(".ProseMirror h2").should("contain.text", "Heading 2");
cy.get(".ProseMirror h3").should("contain.text", "Heading 3");
});

it("renders lists from TinyMCE HTML", () => {
const tinyMCEHtml = `<ul><li>Item 1</li><li>Item 2</li></ul><ol><li>First</li><li>Second</li></ol>`;

cy.mount(TiptapInline, {
props: {
modelValue: tinyMCEHtml,
},
global: {
plugins: [store],
},
});

cy.get(".ProseMirror ul li").should("have.length", 2);
cy.get(".ProseMirror ul li").first().should("contain.text", "Item 1");
cy.get(".ProseMirror ol li").should("have.length", 2);
cy.get(".ProseMirror ol li").first().should("contain.text", "First");
});

it("renders links from TinyMCE HTML", () => {
const tinyMCEHtml = `<p>Visit <a href="https://example.com" target="_blank">this link</a></p>`;

cy.mount(TiptapInline, {
props: {
modelValue: tinyMCEHtml,
},
global: {
plugins: [store],
},
});

cy.get(".ProseMirror a")
.should("have.attr", "href", "https://example.com")
.and("have.attr", "target", "_blank")
.and("contain.text", "this link");
});

it("renders images from TinyMCE HTML", () => {
const tinyMCEHtml = `<p><img src="https://example.com/image.png" alt="Test image" /></p>`;

cy.mount(TiptapInline, {
props: {
modelValue: tinyMCEHtml,
},
global: {
plugins: [store],
},
});

cy.get(".ProseMirror img")
.should("have.attr", "src", "https://example.com/image.png")
.and("have.attr", "alt", "Test image");
});

it("renders tables from TinyMCE HTML", () => {
const tinyMCEHtml = `
<table>
<thead>
<tr>
<th>Header 1</th>
<th>Header 2</th>
</tr>
</thead>
<tbody>
<tr>
<td>Cell 1</td>
<td>Cell 2</td>
</tr>
</tbody>
</table>
`;

cy.mount(TiptapInline, {
props: {
modelValue: tinyMCEHtml,
},
global: {
plugins: [store],
},
});

cy.get(".ProseMirror table").should("exist");
cy.get(".ProseMirror th").should("have.length", 2);
cy.get(".ProseMirror th").first().should("contain.text", "Header 1");
cy.get(".ProseMirror td").should("have.length", 2);
cy.get(".ProseMirror td").first().should("contain.text", "Cell 1");
});

it("renders colored text from TinyMCE HTML", () => {
const tinyMCEHtml = `<p><span style="color: rgb(255, 0, 0);">Red text</span></p>`;

cy.mount(TiptapInline, {
props: {
modelValue: tinyMCEHtml,
},
global: {
plugins: [store],
},
});

cy.get(".ProseMirror span").should("have.attr", "style").and("include", "color");
});

it("renders horizontal rules from TinyMCE HTML", () => {
const tinyMCEHtml = `<p>Before</p><hr /><p>After</p>`;

cy.mount(TiptapInline, {
props: {
modelValue: tinyMCEHtml,
},
global: {
plugins: [store],
},
});

cy.get(".ProseMirror hr").should("exist");
});

it("renders cross-references from datalab HTML", () => {
const datalabHtml = `<p>See sample <span data-type="crossreference" data-item-id="sample1" data-item-type="samples" data-name="Test Sample" data-chemform="H2O"></span> for details</p>`;

cy.mount(TiptapInline, {
props: {
modelValue: datalabHtml,
},
global: {
plugins: [store],
},
});

cy.get(".ProseMirror .cross-reference-wrapper").should("exist");
cy.get(".formatted-item-name").should("contain.text", "sample1");
});

it("renders complex nested formatting from TinyMCE HTML", () => {
const tinyMCEHtml = `
<h2>Complex Document</h2>
<p>This is a <strong>bold and <em>italic</em></strong> text with <a href="https://example.com">a link</a>.</p>
<ul>
<li><strong>Bold list item</strong></li>
<li><em>Italic list item</em></li>
</ul>
<table>
<tr>
<td><strong>Bold cell</strong></td>
<td><span style="color: rgb(0, 0, 255);">Blue cell</span></td>
</tr>
</table>
`;

cy.mount(TiptapInline, {
props: {
modelValue: tinyMCEHtml,
},
global: {
plugins: [store],
},
});

cy.get(".ProseMirror h2").should("contain.text", "Complex Document");
cy.get(".ProseMirror strong").should("exist");
cy.get(".ProseMirror em").should("exist");
cy.get(".ProseMirror a").should("have.attr", "href");
cy.get(".ProseMirror ul li").should("have.length", 2);
cy.get(".ProseMirror table td").should("have.length", 2);
});

it("handles empty content gracefully", () => {
cy.mount(TiptapInline, {
props: {
modelValue: "",
},
global: {
plugins: [store],
},
});

cy.get(".ProseMirror").should("exist");
});
});
12 changes: 10 additions & 2 deletions webapp/cypress/e2e/editPage.cy.js
Original file line number Diff line number Diff line change
Expand Up @@ -226,18 +226,26 @@ describe("Edit Page", () => {
cy.get(".datablock-content div").eq(0).type("\nThe first comment box; further changes.");
cy.contains("Unsaved changes");

cy.get('[data-testid="block-description"]').eq(0).type("The second comment box");
cy.get('[data-testid="block-description"]').first().find(".ProseMirror").click();

cy.get('[data-testid="block-description"]')
.first()
.find(".ProseMirror")
.type("The second comment box");
cy.contains("Unsaved changes");
cy.get('.datablock-header [aria-label="updateBlock"]').eq(1).click();
cy.wait(500).then(() => {
cy.contains("Unsaved changes"); // unsaved changes warning should still exist since first block is still edited
});
cy.get('.datablock-header [aria-label="updateBlock"]').eq(0).click();
cy.contains("Unsaved changes").should("not.exist");
cy.get('[data-testid="block-description"]').first().find(".ProseMirror").click();

cy.get('[data-testid="block-description"]')
.eq(0)
.first()
.find(".ProseMirror")
.type("\nThe second comment box; further changes");

cy.findByLabelText("Name").type("name change");
cy.contains("Unsaved changes");

Expand Down
25 changes: 21 additions & 4 deletions webapp/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,17 +10,32 @@
"lint": "vue-cli-service lint"
},
"resolutions": {
"cross-spawn": "^7.0.5"
"cross-spawn": "^7.0.5",
"prosemirror-view": "1.41.1"
},
"dependencies": {
"@floating-ui/dom": "^1.7.4",
"@fortawesome/fontawesome-svg-core": "^1.2.26-2",
"@fortawesome/free-brands-svg-icons": "^5.15.4",
"@fortawesome/free-regular-svg-icons": "^5.15.2",
"@fortawesome/free-solid-svg-icons": "^5.12.0-2",
"@fortawesome/vue-fontawesome": "^3.0.0-3",
"@popperjs/core": "^2.11.8",
"@primevue/themes": "^4.0.0",
"@tinymce/tinymce-vue": "^4.0.0",
"@tiptap/core": "^3.6.1",
"@tiptap/extension-color": "^3.6.1",
"@tiptap/extension-highlight": "^3.6.1",
"@tiptap/extension-image": "^3.6.1",
"@tiptap/extension-link": "^3.6.1",
"@tiptap/extension-mathematics": "^3.6.1",
"@tiptap/extension-placeholder": "^3.6.1",
"@tiptap/extension-table": "^3.6.1",
"@tiptap/extension-text-style": "^3.6.1",
"@tiptap/extension-typography": "^3.6.1",
"@tiptap/extension-underline": "^3.6.1",
"@tiptap/pm": "^3.6.1",
"@tiptap/starter-kit": "^3.6.1",
"@tiptap/vue-3": "^3.6.1",
"@uppy/core": "^4.4.6",
"@uppy/dashboard": "^4.3.4",
"@uppy/webcam": "^4.2.0",
Expand All @@ -36,14 +51,16 @@
"date-fns": "^2.29.3",
"highlight.js": "^11.7.0",
"js-md5": "^0.8.3",
"markdown-it": "^13.0.1",
"katex": "^0.16.22",
"markdown-it": "^14.1.0",
"mermaid": "^11.10.0",
"primeicons": "^7.0.0",
"primevue": "^4.0.0",
"process": "^0.11.10",
"prosemirror-tables": "^1.8.1",
"qrcode-vue3": "^1.6.8",
"serve": "^14.2.1",
"tinymce": "^5.10.9",
"turndown": "^7.2.1",
"vue": "^3.2.4",
"vue-qrcode-reader": "^5.5.7",
"vue-router": "^4.0.0-0",
Expand Down
10 changes: 9 additions & 1 deletion webapp/public/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,14 @@
>
</noscript>
<div id="app"></div>
<!-- built files will be auto injected -->

<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/mermaid.min.js"></script>
<script>
window.mermaid.initialize({
startOnLoad: false,
theme: "default",
securityLevel: "loose",
});
</script>
</body>
</html>
8 changes: 4 additions & 4 deletions webapp/src/components/CellInformation.vue
Original file line number Diff line number Diff line change
Expand Up @@ -85,10 +85,10 @@
<div class="row">
<div class="col">
<label id="cell-description-label">Description</label>
<TinyMceInline
<TiptapInline
v-model="SampleDescription"
aria-labelledby="cell-description-label"
></TinyMceInline>
></TiptapInline>
</div>
</div>
</div>
Expand All @@ -106,7 +106,7 @@
<script>
import { createComputedSetterForItemField } from "@/field_utils.js";
import ChemFormulaInput from "@/components/ChemFormulaInput";
import TinyMceInline from "@/components/TinyMceInline";
import TiptapInline from "@/components/TiptapInline";
import CellPreparationInformation from "@/components/CellPreparationInformation";
import TableOfContents from "@/components/TableOfContents";
import ItemRelationshipVisualization from "@/components/ItemRelationshipVisualization";
Expand All @@ -118,7 +118,7 @@ import { cellFormats } from "@/resources.js";
export default {
components: {
ChemFormulaInput,
TinyMceInline,
TiptapInline,
CellPreparationInformation,
TableOfContents,
ItemRelationshipVisualization,
Expand Down
6 changes: 3 additions & 3 deletions webapp/src/components/CellPreparationInformation.vue
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@

<div class="form-group ml-5 mt-3">
<label id="synthesis-procedure-label" class="subheading">Procedure</label>
<TinyMceInline
<TiptapInline
v-model="CellPreparationDescription"
aria-labelledby="synthesis-procedure-label"
/>
Expand All @@ -58,7 +58,7 @@
</template>

<script>
import TinyMceInline from "@/components/TinyMceInline";
import TiptapInline from "@/components/TiptapInline";
// import ChemicalFormula from "@/components/ChemicalFormula.vue";
import { createComputedSetterForItemField } from "@/field_utils.js";

Expand All @@ -69,7 +69,7 @@ import CompactConstituentTable from "@/components/CompactConstituentTable";

export default {
components: {
TinyMceInline,
TiptapInline,
CompactConstituentTable,
},
props: {
Expand Down
Loading