diff --git a/.changeset/three-dolls-flash.md b/.changeset/three-dolls-flash.md new file mode 100644 index 00000000..e02c0cf3 --- /dev/null +++ b/.changeset/three-dolls-flash.md @@ -0,0 +1,12 @@ +--- +"rhino-editor": patch +--- + +- `previewable` attribute is now more consistently applied and checked on attachments. +- Fixed a bug where attachments were not rendering properly when the raw action-text HTML passed to the editor. +- Fixed a bug where all attachments were not being properly rendered. +- Figcaption now jumps to the end of the block when you click on the `figure` +- `"Add a caption"` will no longer show up on custom attachments. +- Added a note about custom attachments need an actual content-type unlike Trix. +- Added a small amount of `margin-top` to `figcaption` to match Trix. +- `toMemorySize` now does not return decimales for KB / MB sizes. This is to align with Trix. diff --git a/docs/src/_documentation/references/01-why-rhino-editor.md b/docs/src/_documentation/references/01-why-rhino-editor.md index e24398f9..f8b3f266 100644 --- a/docs/src/_documentation/references/01-why-rhino-editor.md +++ b/docs/src/_documentation/references/01-why-rhino-editor.md @@ -3,7 +3,7 @@ title: Why Rhino Editor? permalink: /references/why-rhino-editor/ --- -Rhino Editor is intended to be a drop-in replacement for Trix. +Rhino Editor is intended to be a (mostly) drop-in replacement for Trix. The current standard WYSIWYG editor for Rails. Trix provides a solid foundation to get started with WYSIWYG editing in Rails. diff --git a/docs/src/_documentation/references/05-differences-from-trix.md b/docs/src/_documentation/references/05-differences-from-trix.md new file mode 100644 index 00000000..e6b15dbc --- /dev/null +++ b/docs/src/_documentation/references/05-differences-from-trix.md @@ -0,0 +1,47 @@ +--- +title: Differences from Trix +permalink: /references/differences-from-trix/ +--- + +<%= render Alert.new(type: "primary") do %> + This section is still in progress and incomplete. This will be a running list of differences, features, + where Rhino has diverged, etc, from Trix. +<% end %> + + +## Custom Attachments + +Custom Attachments in Rhino Editor must have a `content-type` set on them. To do this, go into the Model you are rendering and do the following: + +```rb +class Mention < ApplicationRecord + def attachable_content_type + "application/vnd.active_record.mention" + end +end +``` + +Or using an `ActiveModel::Model` + +```rb +class Mention + include ActiveModel::Model + include ActiveModel::Attributes + include GlobalID::Identification + include ActionText::Attachable + + def attachable_content_type + "application/vnd.active_record.mention" + end +end +``` + +The reason for this is because sometimes you may want to render an ActiveStorage attachment from your server into +Rhino Editor, but don't want it to go through the default image processing, for example, Mentions. + +This is an active choice to break backwards compatibility, but it's intended to allow for more powerful +interactions with custom extensions on TipTap. + +For more context, check out this issue: + + diff --git a/src/exports/attachment-manager.ts b/src/exports/attachment-manager.ts index babe996c..8330fb87 100644 --- a/src/exports/attachment-manager.ts +++ b/src/exports/attachment-manager.ts @@ -20,6 +20,7 @@ export interface AttachmentManagerAttributes { url?: Maybe; width?: Maybe; height?: Maybe; + previewable?: Maybe; } /** @@ -49,6 +50,8 @@ export class AttachmentManager implements AttachmentManagerAttributes { url: null, ...obj, }; + + this.attributes.previewable = this.isPreviewable; } setUploadProgress(progress: number): void { @@ -71,6 +74,7 @@ export class AttachmentManager implements AttachmentManagerAttributes { this.setNodeMarkup({ sgid: this.attributes.sgid, content: this.attributes.content, + previewable: this.isPreviewable, }); return; @@ -82,14 +86,7 @@ export class AttachmentManager implements AttachmentManagerAttributes { if (!obj.url) { return; } - - const isPreviewable = ( - this.constructor as unknown as typeof AttachmentManager - ).isPreviewable; - - const contentType = this.contentType; - - if (contentType && isPreviewable(contentType)) { + if (this.isPreviewable) { /** This preloads the image so we don't show a big flash. */ const image = new Image(); @@ -107,6 +104,7 @@ export class AttachmentManager implements AttachmentManagerAttributes { width: this.attributes.width, height: this.attributes.height, contentType: this.contentType, + previewable: this.isPreviewable, }); image.remove(); }; @@ -120,6 +118,7 @@ export class AttachmentManager implements AttachmentManagerAttributes { sgid: this.attributes.sgid, url: this.attributes.url, contentType: this.contentType, + previewable: this.isPreviewable, }); } diff --git a/src/exports/elements/tip-tap-editor-base.ts b/src/exports/elements/tip-tap-editor-base.ts index 31dc8d33..ee735253 100644 --- a/src/exports/elements/tip-tap-editor-base.ts +++ b/src/exports/elements/tip-tap-editor-base.ts @@ -502,11 +502,15 @@ export class TipTapEditorBase extends BaseElement { } }); - doc.querySelectorAll("figure .attachment__name").forEach((el) => { - if (el.textContent?.includes(" · ") === false) return; - - el.insertAdjacentText("beforeend", " · "); - }); + doc + .querySelectorAll( + "figure :not(.attachment__caption--edited) .attachment__name", + ) + .forEach((el) => { + if (el.textContent?.includes(" · ") === false) return; + + el.insertAdjacentText("beforeend", " · "); + }); const body = doc.querySelector("body"); diff --git a/src/exports/extensions/attachment.ts b/src/exports/extensions/attachment.ts index 078e17cb..408a01f1 100644 --- a/src/exports/extensions/attachment.ts +++ b/src/exports/extensions/attachment.ts @@ -80,9 +80,45 @@ export const figureTypes = [ "attachment-figure", ]; -function getAttributes(node: HTMLElement | string, shouldPreview: boolean) { +/** + * This is a special case where it exists as: + * figure["data-trix-attachment"]["contentType"] and + * action-text-attachment["content-type"] + */ +function parseContentTypeFromElement(element: HTMLElement) { + return ( + findAttribute(element, "content-type") || + JSON.parse(element.getAttribute("data-trix-attachment") || "{}") + .contentType || + "application/octet-stream" + ); +} + +const canParseAttachment = ( + node: HTMLElement | string, + shouldPreview: boolean, +) => { if (node instanceof HTMLElement) { - if (findAttribute(node, "contentType") === "application/octet-stream") { + const contentType = parseContentTypeFromElement(node); + + if (contentType === "application/octet-stream") { + return false; + } + + // For + const actionTextAttachment = node.closest("action-text-attachment"); + if (actionTextAttachment) { + const previewable = + actionTextAttachment.getAttribute("previewable") === "true"; + + if (!actionTextAttachment.getAttribute("sgid")) { + return false; + } + + if (previewable === shouldPreview) { + return true; + } + return false; } @@ -92,18 +128,12 @@ function getAttributes(node: HTMLElement | string, shouldPreview: boolean) { ); if (previewable === shouldPreview) { - var obj = {}; - var attributes = node.attributes; - for (var i = 0, len = attributes.length; i < len; i++) { - // @ts-expect-error - obj[attributes[i].name] = attributes[i].value; - } - return obj; + return true; } } return false; -} +}; /** * This appends to the current HTML of the
into node.attrs.caption. @@ -146,8 +176,10 @@ function handleCaptions( return modified; } -function canPreview(previewable: Boolean, contentType: Maybe): Boolean { - return previewable || AttachmentManager.isPreviewable(contentType || ""); +function canPreview(previewable: boolean, contentType: Maybe): boolean { + return Boolean( + previewable || AttachmentManager.isPreviewable(contentType || ""), + ); } function toExtension(fileName: Maybe): string { @@ -330,23 +362,43 @@ export const Attachment = Node.create({ parseHTML() { return [ - // Generated by #to_trix_html + // When it's
its coming from `to_trix_html` { - // context: https://github.com/KonnorRogers/rhino-editor/pull/112 - tag: "figure[data-trix-attachment]:not([data-trix-content-type='application/octet-stream'])", + tag: "figure[data-trix-attachment]", getAttrs: (node) => { - const attrs = getAttributes(node, this.options.previewable); - return attrs; + const isValid = canParseAttachment(node, this.options.previewable); + + if (!isValid) { + return false; + } + + return null; }, - // contentElement: "figcaption" }, - // Generated by the standard output. + // When it's .attachment, its coming from
its the raw HTML. { - tag: "figure.attachment", + tag: "action-text-attachment > figure.attachment", contentElement: "figcaption", getAttrs: (node) => { - const attrs = getAttributes(node, this.options.previewable); - return attrs; + const isValid = canParseAttachment(node, this.options.previewable); + + if (!isValid) { + return false; + } + + return null; + }, + }, + { + tag: "action-text-attachment", + getAttrs: (node) => { + const isValid = canParseAttachment(node, this.options.previewable); + + if (!isValid) { + return false; + } + + return null; }, }, ]; @@ -446,7 +498,13 @@ export const Attachment = Node.create({ }, progress: { default: 0, - parseHTML: (element) => (findAttribute(element, "sgid") ? 100 : 0), + parseHTML: (element) => { + return findAttribute(element, "sgid") || + findAttribute(element, "content") || + element.closest("action-text-attachment")?.innerHTML + ? 100 + : 0; + }, }, loadingState: { default: LOADING_STATES.notStarted, @@ -476,15 +534,7 @@ export const Attachment = Node.create({ contentType: { default: "", parseHTML: (element) => { - // This is a special case where it exists as: - // figure["data-trix-attachment"]["contentType"] and - // action-text-attachment["content-type"] - return ( - findAttribute(element, "content-type") || - JSON.parse(element.getAttribute("data-trix-attachment") || "") - .contentType || - "application/octet-stream" - ); + return parseContentTypeFromElement(element); }, }, fileName: { @@ -498,11 +548,30 @@ export const Attachment = Node.create({ content: { default: "", parseHTML: (element) => { - return ( - findAttribute(element, "content") || - element.closest("action-text-attachment")?.innerHTML || - "" - ); + const attachment = element.closest("action-text-attachment"); + + let content = ""; + + if (attachment) { + const domParser = new DOMParser(); + const parsedDom = domParser.parseFromString( + attachment.innerHTML, + "text/html", + ); + + const firstChild = parsedDom.body.firstElementChild; + + if (firstChild) { + if ( + firstChild.tagName.toLowerCase() !== "figure" || + !firstChild.classList.contains("attachment") + ) { + content = attachment.innerHTML; + } + } + } + + return content || findAttribute(element, "content"); }, }, url: { @@ -514,10 +583,14 @@ export const Attachment = Node.create({ previewable: { default: false, parseHTML: (element) => { - const { previewable } = JSON.parse( + let { previewable } = JSON.parse( element.getAttribute("data-trix-attachment") || "{}", ); + if (previewable == null) { + previewable = findAttribute(element, "previewable"); + } + return previewable; }, }, @@ -579,11 +652,27 @@ export const Attachment = Node.create({ if (typeof getPos === "function") { const { view } = editor; - view.dispatch( - view.state.tr.setSelection( - TextSelection.create(view.state.doc, getPos() + 1), + + const { tr } = view.state; + + const captionNode = view.state.doc.nodeAt(getPos() + 1); + captionNode?.nodeSize; + + tr.setSelection( + TextSelection.create( + view.state.doc, + getPos() + 1 + (captionNode?.nodeSize || 0), ), ); + + view.dispatch(tr); + + // This is for raw HTML, its kinda not a huge deal... + // const defaultCaption = toDefaultCaption({ fileName, fileSize }) + // if (figcaption.innerHTML === defaultCaption || figcaption.innerHTML === defaultCaption.split(" · ").join(" ")) { + // + // // view.dispatch(tr.setNodeMarkup(getPos(), null, { caption: "" })) + // } } } @@ -640,7 +729,7 @@ export const Attachment = Node.create({ file-name=${fileName || ""} file-size=${String(fileSize || 0)} loading-state=${loadingState || LOADING_STATES.notStarted} - progress=${String(sgid ? 100 : progress)} + progress=${String(sgid || content || !fileSize ? 100 : progress)} contenteditable="false" ?show-metadata=${isPreviewable} .fileUploadErrorMessage=${this.options.fileUploadErrorMessage} @@ -648,7 +737,7 @@ export const Attachment = Node.create({ ${when( - content || !isPreviewable, + content && !isPreviewable, /* 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 */ @@ -667,7 +756,10 @@ export const Attachment = Node.create({ )}
@@ -746,6 +838,7 @@ export const PreviewableAttachment = Attachment.extend({ previewable: true, }; }, + // We purposely override this to nothing. Because all of the extensions registered by Attachment // are global, they run twice. We don't want that. for example, this causes `rhino-attachment-remove` // to fire twice. No bueno. @@ -808,7 +901,7 @@ function handleAttachment( let attachmentNodes: ProseMirrorNode[] = []; - let currGalleryOfNodes: ProseMirrorNode[] = []; + let previewableNodes: ProseMirrorNode[] = []; attachments.forEach((attachment) => { const nodeType = attachment.isPreviewable @@ -829,18 +922,18 @@ function handleAttachment( allNodesPreviewable = false; // Make a new gallery. Non-previewable nodes dont belong in galleries. - if (currGalleryOfNodes.length >= 1) { + if (previewableNodes.length >= 1) { attachmentNodes = attachmentNodes.concat( - schema.nodes["attachment-gallery"].create({}, currGalleryOfNodes), + schema.nodes["attachment-gallery"].create({}, previewableNodes), ); - currGalleryOfNodes = []; + previewableNodes = []; } attachmentNodes.push(figure); return; } - currGalleryOfNodes.push(figure); + previewableNodes.push(figure); }); let end = 0; @@ -863,12 +956,12 @@ function handleAttachment( if (isInGallery) { if (allNodesPreviewable) { - tr.insert(end, attachmentNodes); + tr.insert(end, previewableNodes); } else { // Make a new gallery. Non-previewable nodes dont belong in galleries. - if (!hasGalleriesDisabled && currGalleryOfNodes.length >= 1) { + if (!hasGalleriesDisabled && previewableNodes.length >= 1) { attachmentNodes = attachmentNodes.concat( - schema.nodes["attachment-gallery"].create({}, currGalleryOfNodes), + schema.nodes["attachment-gallery"].create({}, previewableNodes), ); } tr.insert(end + 1, attachmentNodes); @@ -877,9 +970,9 @@ function handleAttachment( const currSelection = state.selection; // Make a new gallery. Non-previewable nodes dont belong in galleries. - if (!hasGalleriesDisabled && currGalleryOfNodes.length >= 1) { + if (!hasGalleriesDisabled && previewableNodes.length >= 1) { attachmentNodes = attachmentNodes.concat( - schema.nodes["attachment-gallery"].create({}, currGalleryOfNodes), + schema.nodes["attachment-gallery"].create({}, previewableNodes), ); } diff --git a/src/exports/styles/trix-core.css b/src/exports/styles/trix-core.css index c3f34c1d..69690bf5 100644 --- a/src/exports/styles/trix-core.css +++ b/src/exports/styles/trix-core.css @@ -22,7 +22,6 @@ } .trix-content figcaption { - margin-top: 0.5em; line-break: anywhere; display: inline-block; white-space: normal; @@ -48,7 +47,8 @@ } /* Attachments */ -.trix-content:not([readonly]) figure:is(:focus-within, :focus, .has-focus) img { +.trix-content:not([readonly]) + figure:is(:focus-within, :focus, .has-focus):nth-child(2) { outline: transparent; box-shadow: var(--rhino-focus-ring); } @@ -183,6 +183,7 @@ } .trix-content .attachment__caption { text-align: center; + margin-top: 0.5em; } .trix-content .attachment__caption diff --git a/src/exports/styles/trix.css b/src/exports/styles/trix.css index 6e2108b9..e81cd719 100644 --- a/src/exports/styles/trix.css +++ b/src/exports/styles/trix.css @@ -23,7 +23,6 @@ } .trix-content figcaption { - margin-top: 0.5em; line-break: anywhere; display: inline-block; white-space: normal; @@ -49,7 +48,8 @@ } /* Attachments */ -.trix-content:not([readonly]) figure:is(:focus-within, :focus, .has-focus) img { +.trix-content:not([readonly]) + figure:is(:focus-within, :focus, .has-focus):nth-child(2) { outline: transparent; box-shadow: var(--rhino-focus-ring); } @@ -184,6 +184,7 @@ } .trix-content .attachment__caption { text-align: center; + margin-top: 0.5em; } .trix-content .attachment__caption diff --git a/src/internal/to-memory-size.ts b/src/internal/to-memory-size.ts index e6c75cad..0cecd2b3 100644 --- a/src/internal/to-memory-size.ts +++ b/src/internal/to-memory-size.ts @@ -11,8 +11,8 @@ export function toMemorySize(bytes: number) { const megabytes = kilobytes / 1024; if (megabytes < 1) { - return kilobytes.toFixed(2).toString() + " KB"; + return kilobytes.toFixed(0).toString() + " KB"; } - return megabytes.toFixed(2).toString() + " MB"; + return megabytes.toFixed(0).toString() + " MB"; } diff --git a/tests/fixtures/screenshot-1.png b/tests/fixtures/screenshot-1.png new file mode 100644 index 00000000..92bcb844 Binary files /dev/null and b/tests/fixtures/screenshot-1.png differ diff --git a/tests/fixtures/screenshot-2.png b/tests/fixtures/screenshot-2.png new file mode 100644 index 00000000..ce3b1eb0 Binary files /dev/null and b/tests/fixtures/screenshot-2.png differ diff --git a/tests/fixtures/screenshot-3.png b/tests/fixtures/screenshot-3.png new file mode 100644 index 00000000..4b2be218 Binary files /dev/null and b/tests/fixtures/screenshot-3.png differ diff --git a/tests/rails/app/controllers/youtube_controller.rb b/tests/rails/app/controllers/youtube_controller.rb index 000e1faa..c251675d 100644 --- a/tests/rails/app/controllers/youtube_controller.rb +++ b/tests/rails/app/controllers/youtube_controller.rb @@ -3,7 +3,7 @@ def show @youtube = Youtube.new(id: "dQw4w9WgXcQ") render json: { sgid: @youtube.attachable_sgid, - content: render_to_string(partial: "youtubes/thumbnail", locals: { youtube: @youtube }, formats: [:html]) + content: render_to_string(partial: "youtubes/thumbnail", locals: { youtube: @youtube }, formats: [:html]), } end end diff --git a/tests/rails/app/frontend/controllers/embed_controller.js b/tests/rails/app/frontend/controllers/embed_controller.js index a5cde112..db793f97 100644 --- a/tests/rails/app/frontend/controllers/embed_controller.js +++ b/tests/rails/app/frontend/controllers/embed_controller.js @@ -34,6 +34,7 @@ export default class EmbedController extends Controller { async embed() { const attrs = await this.fetch() + console.log(attrs) let trixAttachment = new Trix.Attachment({ ...attrs }) const trix = document.querySelector("trix-editor") trix.editor.insertAttachment(trixAttachment) diff --git a/tests/rails/app/models/youtube.rb b/tests/rails/app/models/youtube.rb index fa3b983c..e94c326b 100644 --- a/tests/rails/app/models/youtube.rb +++ b/tests/rails/app/models/youtube.rb @@ -17,4 +17,9 @@ def thumbnail_url def to_trix_content_attachment_partial_path "youtubes/thumbnail" end + + # A custom content type for easy querying + def attachable_content_type + "application/vnd.active_record.youtube" + end end diff --git a/tests/rails/app/views/active_storage/blobs/_blob.html.erb b/tests/rails/app/views/active_storage/blobs/_blob.html.erb index 794c9954..bc384e9f 100644 --- a/tests/rails/app/views/active_storage/blobs/_blob.html.erb +++ b/tests/rails/app/views/active_storage/blobs/_blob.html.erb @@ -3,8 +3,9 @@ <%= image_tag blob.representation(resize_to_limit: local_assigns[:in_gallery] ? [ 800, 600 ] : [ 1024, 768 ]) %> <% end %> -
- <% if caption = blob.try(:caption) %> + <% caption = blob.try(:caption) %> +
"> + <% if caption %> <%= caption.html_safe %> <% else %> <%= blob.filename %> diff --git a/tests/rails/app/views/posts/edit.html.erb b/tests/rails/app/views/posts/edit.html.erb index 4caff367..543efe0e 100644 --- a/tests/rails/app/views/posts/edit.html.erb +++ b/tests/rails/app/views/posts/edit.html.erb @@ -1,4 +1,4 @@ -

Editing post

+

Editing <%= params[:raw] ? "Raw" : "" %> post

<%= render "form", post: @post %> diff --git a/tests/rails/test/fixtures/files/screenshot-1.png b/tests/rails/test/fixtures/files/screenshot-1.png new file mode 100644 index 00000000..92bcb844 Binary files /dev/null and b/tests/rails/test/fixtures/files/screenshot-1.png differ diff --git a/tests/rails/test/fixtures/files/screenshot-2.png b/tests/rails/test/fixtures/files/screenshot-2.png new file mode 100644 index 00000000..ce3b1eb0 Binary files /dev/null and b/tests/rails/test/fixtures/files/screenshot-2.png differ diff --git a/tests/rails/test/fixtures/files/screenshot-3.png b/tests/rails/test/fixtures/files/screenshot-3.png new file mode 100644 index 00000000..4b2be218 Binary files /dev/null and b/tests/rails/test/fixtures/files/screenshot-3.png differ diff --git a/tests/rails/test/fixtures/files/thing.txt b/tests/rails/test/fixtures/files/thing.txt new file mode 100644 index 00000000..c665794a --- /dev/null +++ b/tests/rails/test/fixtures/files/thing.txt @@ -0,0 +1 @@ +placeholder text. How fun. diff --git a/tests/rails/test/system/attachment_attributes_test.rb b/tests/rails/test/system/attachment_attributes_test.rb index 530321d2..3668946e 100644 --- a/tests/rails/test/system/attachment_attributes_test.rb +++ b/tests/rails/test/system/attachment_attributes_test.rb @@ -95,12 +95,17 @@ def attachment_attrs_test attachment_attrs_test - assert page.get_by_text("Editing post") - # 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") attachment_attrs_test + + # 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 + # page.get_by_role('link', name: /Edit raw post/i).click + # assert page.get_by_text("Editing raw post") + # attachment_attrs_test end test "Image attributes" do @@ -135,11 +140,16 @@ def check_attrs check_attrs - assert page.get_by_text("Editing post") - # 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") check_attrs + + # 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 + page.get_by_role('link', name: /Edit raw post/i).click + assert page.get_by_text("Editing raw post") + check_attrs end end diff --git a/tests/rails/test/system/attachment_galleries_test.rb b/tests/rails/test/system/attachment_galleries_test.rb new file mode 100644 index 00000000..5eba4396 --- /dev/null +++ b/tests/rails/test/system/attachment_galleries_test.rb @@ -0,0 +1,170 @@ +require "application_system_test_case" + +class AttachmentGalleriesTest < ApplicationSystemTestCase + def attach_files(files) + rhino_editor = page.expect_file_chooser do + page.locator("rhino-editor [part~='toolbar__button--attach-files']").first.click + end + + files = files.map { |file| file_fixture(file).to_s } + + rhino_editor.set_files(files) + end + + def setup + page.goto(posts_path) + assert page.text_content("h1").include?("Posts") + wait_for_network_idle + end + + test "Should allow to insert multiple attachments in the gallery in sequence" do + page.get_by_role('link', name: /New Post/i).click + + + files = [ + "screenshot-1.png", + ] + + attach_files(files) + + files = [ + "screenshot-2.png", + ] + + attach_files(files) + + files = [ + "screenshot-3.png", + ] + + attach_files(files) + + def check + wait_for_network_idle + + assert page.locator(".attachment-gallery > figure.attachment").nth(0).wait_for(state: "visible") + assert page.locator(".attachment-gallery > figure.attachment").nth(1).wait_for(state: "visible") + assert page.locator(".attachment-gallery > figure.attachment").nth(2).wait_for(state: "visible") + + assert_equal page.locator(".attachment-gallery > figure.attachment").count, 3 + end + + check + + # 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 + + # 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") + + 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.get_by_role('link', name: /Edit raw post/i).click + assert page.get_by_text("Editing raw post") + check + end + + test "Should not allow to insert multiple attachments in the gallery at once" do + page.get_by_role('link', name: /New Post/i).click + + + files = [ + "screenshot-1.png", + "addresses.csv" + ] + + attach_files(files) + + def check + wait_for_network_idle + + assert page.locator(".attachment-gallery > figure.attachment").nth(0).wait_for(state: "visible") + assert page.locator("figure.attachment").nth(1).wait_for(state: "visible") + + assert_equal page.locator(".attachment-gallery > figure.attachment").count, 1 + assert_equal page.locator("figure.attachment").count, 2 + end + + check + + # 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 + + # 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") + + 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.get_by_role('link', name: /Edit raw post/i).click + assert page.get_by_text("Editing raw post") + check + 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 + + wait_for_network_idle + + + files = [ + "screenshot-1.png", + ] + + attach_files(files) + + files = [ + "addresses.csv" + ] + + attach_files(files) + + def check + wait_for_network_idle + + assert page.locator(".attachment-gallery > figure.attachment").nth(0).wait_for(state: 'visible') + assert page.locator("figure.attachment").nth(1).wait_for(state: 'visible') + + assert_equal page.locator(".attachment-gallery > figure.attachment").count, 1 + assert_equal page.locator("figure.attachment").count, 2 + end + + check + + # 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 + + # 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") + + 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.get_by_role('link', name: /Edit raw post/i).click + assert page.get_by_text("Editing raw post") + check + end +end diff --git a/tests/rails/test/system/custom_attachments_test.rb b/tests/rails/test/system/custom_attachments_test.rb new file mode 100644 index 00000000..d8a918be --- /dev/null +++ b/tests/rails/test/system/custom_attachments_test.rb @@ -0,0 +1,41 @@ +require "application_system_test_case" + +class CustomAttachmentsTest < ApplicationSystemTestCase + def setup + page.goto(posts_path) + assert page.text_content("h1").include?("Posts") + wait_for_network_idle + end + + test "Should properly insert a custom attachment" do + page.get_by_role('link', name: /New Post/i).click + + page.locator("rhino-editor").get_by_role("button", name: /Embed/i).click + + def check + assert page.locator("rhino-editor figure.attachment").nth(0).wait_for(state: 'visible') + end + + check + + # 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 + + # 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") + + 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 + page.get_by_role('link', name: /Edit raw post/i).click + assert page.get_by_text("Editing raw post") + check + end +end