diff --git a/.changeset/long-snails-hunt.md b/.changeset/long-snails-hunt.md new file mode 100644 index 00000000..aba78a80 --- /dev/null +++ b/.changeset/long-snails-hunt.md @@ -0,0 +1,5 @@ +--- +"rhino-editor": patch +--- + +Bug fix: bubble menu will now anchor the `
` or an attachment. diff --git a/.changeset/nine-deers-accept.md b/.changeset/nine-deers-accept.md new file mode 100644 index 00000000..250c5f83 --- /dev/null +++ b/.changeset/nine-deers-accept.md @@ -0,0 +1,5 @@ +--- +"rhino-editor": patch +--- + +Style fix: attachments without previews no longer have weird empty border lines diff --git a/.changeset/nine-turtles-teach.md b/.changeset/nine-turtles-teach.md new file mode 100644 index 00000000..ba81ea52 --- /dev/null +++ b/.changeset/nine-turtles-teach.md @@ -0,0 +1,5 @@ +--- +"rhino-editor": minor +--- + +Feature: Added the `determineNodeViewAnchor` to the bubble menu extension diff --git a/.changeset/serious-hairs-knock.md b/.changeset/serious-hairs-knock.md new file mode 100644 index 00000000..7eb9a94e --- /dev/null +++ b/.changeset/serious-hairs-knock.md @@ -0,0 +1,5 @@ +--- +"rhino-editor": patch +--- + +Bug Fix: no longer generate an empty `` for non-previewable attachments. diff --git a/.changeset/tricky-years-wave.md b/.changeset/tricky-years-wave.md new file mode 100644 index 00000000..5e6a239d --- /dev/null +++ b/.changeset/tricky-years-wave.md @@ -0,0 +1,5 @@ +--- +"rhino-editor": minor +--- + +Feature: Add the `defer-initialize` attribute for more reliable initialization events. diff --git a/docs/src/_documentation/references/04-modifying-the-editor.md b/docs/src/_documentation/references/04-modifying-the-editor.md index 674299ca..80c8be11 100644 --- a/docs/src/_documentation/references/04-modifying-the-editor.md +++ b/docs/src/_documentation/references/04-modifying-the-editor.md @@ -9,6 +9,10 @@ There are 3 general ways to wait for RhinoEditor to connect to the DOM and then - `rhino-initialize` - `` after the TipTap instance is created - `customElements.whenDefined("rhino-editor").then(() => { document.querySelectorAll("rhino-editor") })` This is a general purpose callback you can use. The TipTap instance more or may not created when this is called. +<%= render Alert.new(type: :info) do %> +If you are having trouble catching the `rhino-initialize` or the `rhino-before-initialize` events read the section on [Delaying Initialization](#delaying-initialization) +<% end %> + You can use any of these events to modify the editor options. Heres an example for each one to add additional heading levels. @@ -42,3 +46,22 @@ customElements.whenDefined("rhino-editor").then(() => { }) ``` +## Delaying Initialization + +Sometimes it can be quite challenging to catch either the `rhino-initialize` or `rhino-before-initialize` events due to load order of your JavaScript. + +If you add the `defer-initialization` attribute to your editor, the editor will not start until you remove that attribute. + +Like so: + +```html + + + +``` diff --git a/docs/src/rhino-editor/exports/styles/trix.css b/docs/src/rhino-editor/exports/styles/trix.css index 0e317d07..6d3ff0b2 100644 --- a/docs/src/rhino-editor/exports/styles/trix.css +++ b/docs/src/rhino-editor/exports/styles/trix.css @@ -53,7 +53,11 @@ .trix-content .rhino-upload-error { background-color: rgba(255, 0, 0, 0.3); } -.trix-content:not([readonly]) figure:is(:focus-within, :focus, .has-focus) :nth-child(2) { +.trix-content:not([readonly]) .attachment--preview:is(:focus-within, :focus, .has-focus) :is(img) { + outline: transparent; + box-shadow: var(--rhino-focus-ring); +} +.trix-content:not([readonly]) .attachment:not(.attachment--preview):is(:focus-within, :focus, .has-focus) { outline: transparent; box-shadow: var(--rhino-focus-ring); } diff --git a/package.json b/package.json index 392655d8..a0dc51d1 100644 --- a/package.json +++ b/package.json @@ -44,6 +44,7 @@ "@esm-bundle/chai": "4.3.4-fix.0", "@open-wc/testing": "^3.2.2", "@playwright/test": "^1.48.0", + "@types/mocha": "^10.0.9", "@types/rails__activestorage": "^7.1.1", "@typescript-eslint/parser": "^6.21.0", "@web/dev-server": "^0.3.7", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 9aedb752..d1e49381 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -78,6 +78,9 @@ importers: '@playwright/test': specifier: ^1.48.0 version: 1.48.0 + '@types/mocha': + specifier: ^10.0.9 + version: 10.0.9 '@types/rails__activestorage': specifier: ^7.1.1 version: 7.1.1 @@ -971,6 +974,9 @@ packages: '@types/minimist@1.2.5': resolution: {integrity: sha512-hov8bUuiLiyFPGyFPE1lwWhmzYbirOXQNNo40+y3zow8aFVTeyn3VWL0VFFfdNddA8S4Vf0Tc062rzyNr7Paag==} + '@types/mocha@10.0.9': + resolution: {integrity: sha512-sicdRoWtYevwxjOHNMPTl3vSfJM6oyW8o1wXeI7uww6b6xHg8eBznQDNSGBCDJmsE8UMxP05JgZRtsKbTqt//Q==} + '@types/node@12.20.55': resolution: {integrity: sha512-J8xLz7q2OFulZ2cyGTLE1TbbZcjpno7FaN6zdJNrgAdrJ+DZzh/uFR6YrTb4C+nXakvud8Q4+rbhoIWlYQbUFQ==} @@ -4411,6 +4417,8 @@ snapshots: '@types/minimist@1.2.5': {} + '@types/mocha@10.0.9': {} + '@types/node@12.20.55': {} '@types/node@22.7.4': diff --git a/src/exports/elements/tip-tap-editor-base.ts b/src/exports/elements/tip-tap-editor-base.ts index a7d9dbbc..87c4c26f 100644 --- a/src/exports/elements/tip-tap-editor-base.ts +++ b/src/exports/elements/tip-tap-editor-base.ts @@ -66,6 +66,11 @@ export class TipTapEditorBase extends BaseElement { class: { reflect: true }, accept: { reflect: true }, serializer: { reflect: true }, + deferInitialize: { + type: Boolean, + attribute: "defer-initialize", + reflect: true, + }, // Properties editor: { state: true }, @@ -126,6 +131,11 @@ export class TipTapEditorBase extends BaseElement { */ extensions: EditorOptions["extensions"] = []; + /** + * When the `defer-initialize` attribute is present, it will wait to start the TipTap editor until the attribute has been removed. + */ + deferInitialize = false; + /** * @internal */ @@ -315,6 +325,10 @@ export class TipTapEditorBase extends BaseElement { protected willUpdate( changedProperties: PropertyValueMap | Map, ): void { + if (changedProperties.has("deferInitialize") && !this.deferInitialize) { + this.startEditor(); + } + if (changedProperties.has("class")) { this.classList.add("rhino-editor"); } @@ -387,6 +401,12 @@ export class TipTapEditorBase extends BaseElement { this.classList.add("rhino-editor"); + if (!this.deferInitialize) { + this.startEditor(); + } + } + + async startEditor() { await this.updateComplete; setTimeout(() => { diff --git a/src/exports/elements/tip-tap-editor.ts b/src/exports/elements/tip-tap-editor.ts index 0cba8e71..32a7e65a 100644 --- a/src/exports/elements/tip-tap-editor.ts +++ b/src/exports/elements/tip-tap-editor.ts @@ -191,11 +191,18 @@ export class TipTapEditor extends TipTapEditorBase { }, }) as typeof this.starterKitOptions; + document.addEventListener("click", this.__handleLinkDialogClick); + } + + /** + * @override + */ + async startEditor() { + await super.startEditor(); + if (this.editor) { this.editor.on("focus", this.closeLinkDialog); } - - document.addEventListener("click", this.__handleLinkDialogClick); } disconnectedCallback() { @@ -256,8 +263,6 @@ export class TipTapEditor extends TipTapEditorBase { setTimeout(() => { if (inputElement != null) inputElement.focus(); }); - - console.log("showing"); } get linkDialog(): Maybe { @@ -332,7 +337,9 @@ export class TipTapEditor extends TipTapEditorBase { } renderBoldButton(prefix = "") { - const boldEnabled = Boolean(this.editor?.commands.toggleBold); + const boldEnabled = + this.starterKitOptions.bold !== false || + Boolean(this.editor?.commands.toggleBold); if (!boldEnabled) return html``; @@ -384,7 +391,9 @@ export class TipTapEditor extends TipTapEditorBase { } renderItalicButton(prefix = "") { - const italicEnabled = Boolean(this.editor?.commands.toggleItalic); + const italicEnabled = + this.starterKitOptions.italic !== false || + Boolean(this.editor?.commands.toggleItalic); if (!italicEnabled) return html``; @@ -440,7 +449,9 @@ export class TipTapEditor extends TipTapEditorBase { } renderStrikeButton(prefix = "") { - const strikeEnabled = Boolean(this.editor?.commands.toggleStrike); + const strikeEnabled = + this.starterKitOptions.rhinoStrike !== false || + Boolean(this.editor?.commands.toggleStrike); if (!strikeEnabled) return html``; @@ -495,7 +506,9 @@ export class TipTapEditor extends TipTapEditorBase { } renderLinkButton(prefix = "") { - const linkEnabled = Boolean(this.editor?.commands.setLink); + const linkEnabled = + this.starterKitOptions.rhinoLink !== false || + Boolean(this.editor?.commands.setLink); if (!linkEnabled) return html``; @@ -553,7 +566,9 @@ export class TipTapEditor extends TipTapEditorBase { } renderHeadingButton(prefix = "") { - const headingEnabled = Boolean(this.editor?.commands.toggleHeading); + const headingEnabled = + this.starterKitOptions.heading !== false || + Boolean(this.editor?.commands.toggleHeading); if (!headingEnabled) return html``; @@ -610,7 +625,9 @@ export class TipTapEditor extends TipTapEditorBase { } renderBlockquoteButton(prefix = "") { - const blockQuoteEnabled = Boolean(this.editor?.commands.toggleBlockquote); + const blockQuoteEnabled = + this.starterKitOptions.blockquote !== false || + Boolean(this.editor?.commands.toggleBlockquote); if (!blockQuoteEnabled) return html``; @@ -667,7 +684,9 @@ export class TipTapEditor extends TipTapEditorBase { } renderCodeBlockButton(prefix = "") { - const codeBlockEnabled = Boolean(this.editor?.commands.toggleCodeBlock); + const codeBlockEnabled = + this.starterKitOptions.codeBlock !== false || + Boolean(this.editor?.commands.toggleCodeBlock); if (!codeBlockEnabled) return html``; @@ -723,7 +742,9 @@ export class TipTapEditor extends TipTapEditorBase { } renderBulletListButton(prefix = "") { - const bulletListEnabled = Boolean(this.editor?.commands.toggleBulletList); + const bulletListEnabled = + this.starterKitOptions.bulletList !== false || + Boolean(this.editor?.commands.toggleBulletList); if (!bulletListEnabled) return html``; @@ -786,7 +807,9 @@ export class TipTapEditor extends TipTapEditorBase { } renderOrderedListButton(prefix = "") { - const orderedListEnabled = Boolean(this.editor?.commands.toggleOrderedList); + const orderedListEnabled = + this.starterKitOptions.orderedList !== false || + Boolean(this.editor?.commands.toggleOrderedList); if (!orderedListEnabled) return html``; @@ -851,7 +874,9 @@ export class TipTapEditor extends TipTapEditorBase { } renderAttachmentButton(prefix = "") { - const attachmentEnabled = Boolean(this.editor?.commands.setAttachment); + const attachmentEnabled = + this.starterKitOptions.rhinoAttachment !== false || + Boolean(this.editor?.commands.setAttachment); if (!attachmentEnabled) return html``; @@ -912,7 +937,9 @@ export class TipTapEditor extends TipTapEditorBase { } renderUndoButton(prefix = "") { - const undoEnabled = Boolean(this.editor?.commands.undo); + const undoEnabled = + this.starterKitOptions.history !== false || + Boolean(this.editor?.commands.undo); if (!undoEnabled) return html``; @@ -966,10 +993,10 @@ export class TipTapEditor extends TipTapEditorBase { renderDecreaseIndentation(prefix = "") { // Decrease / increase indentation are special cases in that they rely on built-in editor // commands and not commands added by extensions. - const decreaseIndentationNotEnabled = - this.starterKitOptions.decreaseIndentation == false; + const decreaseIndentationEnabled = + this.starterKitOptions.decreaseIndentation !== false; // || Boolean(this.editor?.commands.liftListItem); - if (decreaseIndentationNotEnabled) return html``; + if (!decreaseIndentationEnabled) return html``; const isDisabled = this.editor == null || !this.editor.can().liftListItem("listItem"); @@ -1021,10 +1048,12 @@ export class TipTapEditor extends TipTapEditorBase { } renderIncreaseIndentation(prefix = "") { - const increaseIndentationNotEnabled = - this.starterKitOptions.increaseIndentation == false; + // Decrease / increase indentation are special cases in that they rely on built-in editor + // commands and not commands added by extensions. + const increaseIndentationEnabled = + this.starterKitOptions.increaseIndentation !== false; // || Boolean(this.editor?.commands.sinkListItem); - if (increaseIndentationNotEnabled) return html``; + if (!increaseIndentationEnabled) return html``; const isDisabled = this.editor == null || !this.editor.can().sinkListItem("listItem"); @@ -1076,7 +1105,9 @@ export class TipTapEditor extends TipTapEditorBase { } renderRedoButton(prefix = "") { - const redoEnabled = Boolean(this.editor?.commands.redo); + const redoEnabled = + this.starterKitOptions.history !== false || + Boolean(this.editor?.commands.redo); if (!redoEnabled) return html``; diff --git a/src/exports/extensions/attachment.ts b/src/exports/extensions/attachment.ts index 2dcf67da..4926c001 100644 --- a/src/exports/extensions/attachment.ts +++ b/src/exports/extensions/attachment.ts @@ -756,13 +756,17 @@ export const Attachment = Node.create({ ${when( - content && !isPreviewable, + content, /* This is really not great. This is how Trix does it, but it feels very unsafe. https://github.com/basecamp/trix/blob/fda14c5ae88a0821cf8999a53dcb3572b4172cf0/src/trix/views/attachment_view.js#L36 */ () => html`${unsafeHTML(content)}`, - () => html` - html``, + )} + ${when( + isPreviewable && !content, + () => + html` ({ height=${String(height)} src=${ifDefined(imgSrc)} contenteditable="false" - /> - `, + />`, + () => html``, )}
boolean) | null; + + /** + * A function that determines what DOM Element to use as an anchor for a NodeView. Useful for things like attachments. + */ + determineNodeViewAnchor?: + | ((props: { + editor: Editor; + view: EditorView; + state: EditorState; + oldState?: EditorState; + from: number; + to: number; + }) => HTMLElement | null) + | null; } export type BubbleMenuViewProps = BubbleMenuPluginProps & { @@ -103,6 +117,33 @@ export class BubbleMenuView { return true; }; + public determineNodeViewAnchor: Exclude< + BubbleMenuPluginProps["determineNodeViewAnchor"], + null + > = ({ view, from }) => { + let node = view.nodeDOM(from) as HTMLElement; + + // Attachment node views are special, we dont want to show text editing operations. + // Perhaps in the future we may have a default bubble menu for attachment transforms?? + if ( + this.editor.isActive("attachment-figure") || + this.editor.isActive("previewable-attachment-figure") + ) { + const figcaption = node.querySelector("figcaption"); + node = figcaption || node; + } + + let nodeViewWrapper = node.dataset.nodeViewWrapper + ? node + : node.querySelector("[data-node-view-wrapper]"); + + if (nodeViewWrapper) { + node = nodeViewWrapper.firstChild as HTMLElement; + } + + return node; + }; + constructor({ editor, element, @@ -110,6 +151,7 @@ export class BubbleMenuView { // tippyOptions = {}, updateDelay = 250, shouldShow, + determineNodeViewAnchor, }: BubbleMenuViewProps) { this.editor = editor; this.element = element; @@ -120,6 +162,10 @@ export class BubbleMenuView { this.shouldShow = shouldShow; } + if (determineNodeViewAnchor) { + this.determineNodeViewAnchor = determineNodeViewAnchor; + } + this.view.dom.addEventListener("dragstart", this.dragstartHandler); this.editor.on("focus", this.focusHandler); this.editor.on("blur", this.blurHandler); @@ -223,15 +269,15 @@ export class BubbleMenuView { let clientRect: null | (() => DOMRect) = null; if (isNodeSelection(state.selection)) { - let node = view.nodeDOM(from) as HTMLElement; - - const nodeViewWrapper = node.dataset.nodeViewWrapper - ? node - : node.querySelector("[data-node-view-wrapper]"); - - if (nodeViewWrapper) { - node = nodeViewWrapper.firstChild as HTMLElement; - } + const node = + this.determineNodeViewAnchor?.({ + editor: this.editor, + view, + state, + oldState, + from, + to, + }) || (view.nodeDOM(from) as HTMLElement); if (node) { clientRect = () => node.getBoundingClientRect(); @@ -315,6 +361,7 @@ export const BubbleMenuExtension = Extension.create({ element: this.options.element, updateDelay: this.options.updateDelay, shouldShow: this.options.shouldShow, + determineNodeViewAnchor: this.options.determineNodeViewAnchor, }), ]; }, diff --git a/src/exports/styles/trix-core.css b/src/exports/styles/trix-core.css index e175d100..5cd697d1 100644 --- a/src/exports/styles/trix-core.css +++ b/src/exports/styles/trix-core.css @@ -69,8 +69,14 @@ /* Attachments */ .trix-content:not([readonly]) - figure:is(:focus-within, :focus, .has-focus) - :nth-child(2) { + .attachment--preview:is(:focus-within, :focus, .has-focus) + :is(img) { + outline: transparent; + box-shadow: var(--rhino-focus-ring); +} + +.trix-content:not([readonly]) + .attachment:not(.attachment--preview):is(:focus-within, :focus, .has-focus) { outline: transparent; box-shadow: var(--rhino-focus-ring); } diff --git a/src/exports/styles/trix.css b/src/exports/styles/trix.css index 0110829a..3e1c397b 100644 --- a/src/exports/styles/trix.css +++ b/src/exports/styles/trix.css @@ -70,8 +70,14 @@ /* Attachments */ .trix-content:not([readonly]) - figure:is(:focus-within, :focus, .has-focus) - :nth-child(2) { + .attachment--preview:is(:focus-within, :focus, .has-focus) + :is(img) { + outline: transparent; + box-shadow: var(--rhino-focus-ring); +} + +.trix-content:not([readonly]) + .attachment:not(.attachment--preview):is(:focus-within, :focus, .has-focus) { outline: transparent; box-shadow: var(--rhino-focus-ring); } diff --git a/tests/rails/test/system/attachment_galleries_test.rb b/tests/rails/test/system/attachment_galleries_test.rb index f7da759c..d6891bde 100644 --- a/tests/rails/test/system/attachment_galleries_test.rb +++ b/tests/rails/test/system/attachment_galleries_test.rb @@ -78,111 +78,137 @@ def check end test "Should not allow to insert multiple attachments in the gallery are inserted at the same time" do - page.get_by_role('link', name: /New Post/i).click + def run_test + page.get_by_role('link', name: /New Post/i).click - wait_for_network_idle + wait_for_network_idle - files = [ - "screenshot-1.png", - "addresses.csv" - ] + files = [ + "screenshot-1.png", + "addresses.csv" + ] - page.locator("rhino-editor").nth(0).click - attach_files(files) - page.wait_for_selector(".attachment-gallery .attachment[sgid]", state: "visible") - page.wait_for_selector(":not(.attachment-gallery) .attachment[sgid]", state: "visible") + page.locator("rhino-editor").nth(0).click + attach_files(files) + page.wait_for_selector(".attachment-gallery .attachment[sgid]", state: "visible") + page.wait_for_selector(":not(.attachment-gallery) .attachment[sgid]", state: "visible") - def check - wait_for_network_idle + def check + wait_for_network_idle - page.locator("body").click - assert page.locator(".attachment-gallery .attachment").nth(0).wait_for(state: 'visible') - assert page.locator(".attachment").nth(1).wait_for(state: 'visible') + page.locator("body").click + assert page.locator(".attachment-gallery .attachment").nth(0).wait_for(state: 'visible') + assert page.locator(".attachment").nth(1).wait_for(state: 'visible') - assert_equal page.locator(".attachment-gallery .attachment").count, 1 - assert_equal page.locator(".attachment").count, 2 - page.locator("body").click - end + assert_equal page.locator(".attachment-gallery .attachment").count, 1 + assert_equal page.locator(".attachment").count, 2 + page.locator("body").click + end - check + check - # For some silly reason, this test only fails in GH Actions - return if ENV["CI"] == "true" + # For some silly reason, the next few checks only fail in GH Actions + return if ENV["CI"] == "true" - # Save the attachment, make sure we render properly. - page.get_by_role('button', name: /Create Post/i).click + # Save the attachment, make sure we render properly. + page.get_by_role('button', name: /Create Post/i).click - assert page.get_by_text("Post was successfully created") - check + assert page.get_by_text("Post was successfully created") + check - # Go back and edit the file and make sure it renders properly in editor - page.get_by_role('link', name: /Edit this post/i).click + # Go back and edit the file and make sure it renders properly in editor + page.get_by_role('link', name: /Edit this post/i).click - assert page.get_by_text("Editing post") + assert page.get_by_text("Editing post") - check + check - # Go back and edit the file and make sure it renders properly in editor - page.get_by_role('link', name: /Show this post/i).click - wait_for_network_idle - page.locator("body").click - page.get_by_role('link', name: /Edit raw post/i).click - assert page.get_by_text("Editing raw post") - check + # Go back and edit the file and make sure it renders properly in editor + page.get_by_role('link', name: /Show this post/i).click + wait_for_network_idle + page.locator("body").click + page.get_by_role('link', name: /Edit raw post/i).click + assert page.get_by_text("Editing raw post") + check + end + + count = 0 + + # Hacky retry + begin + run_test + rescue => e + count += 1 + raise e if count > 2 + run_test + end end test "Should not allow to insert multiple attachments in the gallery in sequence" do - page.get_by_role('link', name: /New Post/i).click + def run_test + page.get_by_role('link', name: /New Post/i).click - wait_for_network_idle + wait_for_network_idle - files = [ - "screenshot-1.png", - ] + files = [ + "screenshot-1.png", + ] - attach_files(files) - page.wait_for_selector(".attachment-gallery .attachment[sgid]", state: "visible") + attach_files(files) + page.wait_for_selector(".attachment-gallery .attachment[sgid]", state: "visible") - files = [ - "addresses.csv" - ] + files = [ + "addresses.csv" + ] - attach_files(files) - page.wait_for_selector(":not(.attachment-gallery) .attachment[sgid]", state: "visible") + attach_files(files) + page.wait_for_selector(":not(.attachment-gallery) .attachment[sgid]", state: "visible") - def check - wait_for_network_idle + def check + wait_for_network_idle - page.locator("body").click - assert page.locator(".attachment-gallery .attachment").nth(0).wait_for(state: 'visible') - assert page.locator(".attachment").nth(1).wait_for(state: 'visible') + page.locator("body").click + assert page.locator(".attachment-gallery .attachment").nth(0).wait_for(state: 'visible') + assert page.locator(".attachment").nth(1).wait_for(state: 'visible') - assert_equal page.locator(".attachment-gallery .attachment").count, 1 - assert_equal page.locator(".attachment").count, 2 - page.locator("body").click - end + assert_equal page.locator(".attachment-gallery .attachment").count, 1 + assert_equal page.locator(".attachment").count, 2 + page.locator("body").click + end - check + check - # Save the attachment, make sure we render properly. - page.get_by_role('button', name: /Create Post/i).click + # Save the attachment, make sure we render properly. + page.get_by_role('button', name: /Create Post/i).click - assert page.get_by_text("Post was successfully created") - check + assert page.get_by_text("Post was successfully created") + check - # Go back and edit the file and make sure it renders properly in editor - page.get_by_role('link', name: /Edit this post/i).click + # Go back and edit the file and make sure it renders properly in editor + page.get_by_role('link', name: /Edit this post/i).click - assert page.get_by_text("Editing post") + assert page.get_by_text("Editing post") - check + check - # Go back and edit the file and make sure it renders properly in editor - page.get_by_role('link', name: /Show this post/i).click - wait_for_network_idle - page.locator("body").click - page.get_by_role('link', name: /Edit raw post/i).click - assert page.get_by_text("Editing raw post") - check + # Go back and edit the file and make sure it renders properly in editor + page.get_by_role('link', name: /Show this post/i).click + wait_for_network_idle + page.locator("body").click + page.get_by_role('link', name: /Edit raw post/i).click + assert page.get_by_text("Editing raw post") + check + end + + count = 0 + + # Hacky retry + begin + run_test + rescue => e + count += 1 + raise e if count > 2 + run_test + end end end diff --git a/tests/unit/events.test.js b/tests/unit/events.test.js index 4f479839..758291d3 100644 --- a/tests/unit/events.test.js +++ b/tests/unit/events.test.js @@ -1,6 +1,6 @@ // @ts-check import "rhino-editor" -import { assert, aTimeout, waitUntil } from "@open-wc/testing" +import { assert, aTimeout, fixture, waitUntil } from "@open-wc/testing" import { html } from "lit" import { readFile, sendKeys } from '@web/test-runner-commands'; import sinon from "sinon" @@ -256,3 +256,31 @@ test("rhino-paste", async () => { document.removeEventListener("rhino-paste", handleEvent) }) +test("Should not fire initialize and before-initialize events until after defer-initialize is removed", async () => { + const beforeInitializeSpy = sinon.spy() + const initializeSpy = sinon.spy() + + function handleBeforeInitialize () { beforeInitializeSpy() } + function handleInitialize () { initializeSpy() } + + document.addEventListener("rhino-before-initialize", handleBeforeInitialize) + document.addEventListener("rhino-initialize", handleInitialize) + + const rhinoEditor = await fixture(html``) + + await rhinoEditor.updateComplete + await aTimeout(10) + + assert.isTrue(beforeInitializeSpy.notCalled) + assert.isTrue(initializeSpy.notCalled) + + rhinoEditor.removeAttribute("defer-initialize") + await rhinoEditor.updateComplete + await aTimeout(10) + + assert.isTrue(beforeInitializeSpy.calledOnce) + assert.isTrue(initializeSpy.calledOnce) + + document.removeEventListener("rhino-before-initialize", handleBeforeInitialize) + document.removeEventListener("rhino-initialize", handleInitialize) +})