scribe/spec/components/page_content_spec.cr

238 lines
5.9 KiB
Crystal
Raw Normal View History

First step rendering a page The API responds with a bunch of paragraphs which the client converts into Paragraph objects. This turns the paragraphs in a PostResponse's Paragraph objects into the form needed to render them on a page. This includes converting flat list elements into list elements nested by a UL. And adding a limited markups along the way. The array of paragraphs is passed to a recursive function. The function takes the first paragraph and either wraps the (marked up) contents in a container tag (like Paragraph or Heading3), and then moves onto the next tag. If it finds a list, it starts parsing the next paragraphs as a list instead. Originally, this was implemented like so: ```crystal paragraph = paragraphs.shift if list? convert_list([paragraph] + paragraphs) end ``` However, passing the `paragraphs` after adding it to the already shifted `paragraph` creates a new object. This means `paragraphs` won't be mutated and once the list is parsed, it starts with the next element of the list. Instead, the element is `shift`ed inside each converter. ```crystal if paragraphs.first == list? convert_list(paragraphs) end def convert_list(paragraphs) paragraph = paragraphs.shift # ... end ``` When rendering, there is an Empty and Container object. These represent a kind of "null object" for both leafs and parent objects respectively. They should never actually render. Emptys are filtered out, and Containers are never created explicitly but this will make the types pass. IFrames are a bit of a special case. Each IFrame has custom data on it that this system would need to be aware of. For now, instead of trying to parse the seemingly large number of iframe variations and dealing with embedded iframe problems, this will just keep track of the source page URL and send the user there with a link.
2021-05-16 20:14:25 +02:00
require "../spec_helper"
include Nodes
describe PageContent do
it "renders a single parent/child node structure" do
page = Page.new(nodes: [
Paragraph.new(children: [
Text.new(content: "hi"),
] of Child)
] of Child)
html = PageContent.new(page: page).render_to_string
html.should eq %(<p>hi</p>)
end
it "renders multiple childrens" do
page = Page.new(nodes: [
Paragraph.new(children: [
Text.new(content: "Hello, "),
Emphasis.new(children: [
Text.new(content: "World!")
] of Child)
] of Child),
UnorderedList.new(children: [
ListItem.new(children: [
Text.new(content: "List!")
] of Child),
ListItem.new(children: [
Text.new(content: "Again!"),
] of Child)
] of Child)
] of Child)
html = PageContent.new(page: page).render_to_string
html.should eq %(<p>Hello, <em>World!</em></p><ul><li>List!</li><li>Again!</li></ul>)
end
it "renders an anchor" do
page = Page.new(nodes: [
Anchor.new(href: "https://example.com", text: "link"),
] of Child)
html = PageContent.new(page: page).render_to_string
html.should eq %(<a href="https://example.com">link</a>)
end
First step rendering a page The API responds with a bunch of paragraphs which the client converts into Paragraph objects. This turns the paragraphs in a PostResponse's Paragraph objects into the form needed to render them on a page. This includes converting flat list elements into list elements nested by a UL. And adding a limited markups along the way. The array of paragraphs is passed to a recursive function. The function takes the first paragraph and either wraps the (marked up) contents in a container tag (like Paragraph or Heading3), and then moves onto the next tag. If it finds a list, it starts parsing the next paragraphs as a list instead. Originally, this was implemented like so: ```crystal paragraph = paragraphs.shift if list? convert_list([paragraph] + paragraphs) end ``` However, passing the `paragraphs` after adding it to the already shifted `paragraph` creates a new object. This means `paragraphs` won't be mutated and once the list is parsed, it starts with the next element of the list. Instead, the element is `shift`ed inside each converter. ```crystal if paragraphs.first == list? convert_list(paragraphs) end def convert_list(paragraphs) paragraph = paragraphs.shift # ... end ``` When rendering, there is an Empty and Container object. These represent a kind of "null object" for both leafs and parent objects respectively. They should never actually render. Emptys are filtered out, and Containers are never created explicitly but this will make the types pass. IFrames are a bit of a special case. Each IFrame has custom data on it that this system would need to be aware of. For now, instead of trying to parse the seemingly large number of iframe variations and dealing with embedded iframe problems, this will just keep track of the source page URL and send the user there with a link.
2021-05-16 20:14:25 +02:00
it "renders a blockquote" do
page = Page.new(nodes: [
BlockQuote.new(children: [
Text.new("Wayne Gretzky. Michael Scott.")
] of Child)
] of Child)
html = PageContent.new(page: page).render_to_string
html.should eq %(<blockquote>Wayne Gretzky. Michael Scott.</blockquote>)
end
it "renders code" do
page = Page.new(nodes: [
Code.new(children: [
Text.new("foo = bar")
] of Child)
] of Child)
html = PageContent.new(page: page).render_to_string
html.should eq %(<code>foo = bar</code>)
end
it "renders empasis" do
page = Page.new(nodes: [
Paragraph.new(children: [
Text.new(content: "This is "),
Emphasis.new(children: [
Text.new(content: "neat!")
] of Child),
] of Child),
] of Child)
html = PageContent.new(page: page).render_to_string
html.should eq %(<p>This is <em>neat!</em></p>)
end
it "renders a figure and figure caption" do
page = Page.new(nodes: [
Figure.new(children: [
Image.new(src: "image.png", originalWidth: 100, originalHeight: 100),
FigureCaption.new(children: [
Text.new("A caption")
] of Child),
] of Child),
] of Child)
html = PageContent.new(page: page).render_to_string
html.should eq stripped_html <<-HTML
<figure>
<img src="https://cdn-images-1.medium.com/fit/c/100/100/image.png" width="100" height="100">
<figcaption>A caption</figcaption>
</figure>
HTML
end
First step rendering a page The API responds with a bunch of paragraphs which the client converts into Paragraph objects. This turns the paragraphs in a PostResponse's Paragraph objects into the form needed to render them on a page. This includes converting flat list elements into list elements nested by a UL. And adding a limited markups along the way. The array of paragraphs is passed to a recursive function. The function takes the first paragraph and either wraps the (marked up) contents in a container tag (like Paragraph or Heading3), and then moves onto the next tag. If it finds a list, it starts parsing the next paragraphs as a list instead. Originally, this was implemented like so: ```crystal paragraph = paragraphs.shift if list? convert_list([paragraph] + paragraphs) end ``` However, passing the `paragraphs` after adding it to the already shifted `paragraph` creates a new object. This means `paragraphs` won't be mutated and once the list is parsed, it starts with the next element of the list. Instead, the element is `shift`ed inside each converter. ```crystal if paragraphs.first == list? convert_list(paragraphs) end def convert_list(paragraphs) paragraph = paragraphs.shift # ... end ``` When rendering, there is an Empty and Container object. These represent a kind of "null object" for both leafs and parent objects respectively. They should never actually render. Emptys are filtered out, and Containers are never created explicitly but this will make the types pass. IFrames are a bit of a special case. Each IFrame has custom data on it that this system would need to be aware of. For now, instead of trying to parse the seemingly large number of iframe variations and dealing with embedded iframe problems, this will just keep track of the source page URL and send the user there with a link.
2021-05-16 20:14:25 +02:00
it "renders an H3" do
page = Page.new(nodes: [
Heading3.new(children: [
Text.new(content: "Title!"),
] of Child),
] of Child)
html = PageContent.new(page: page).render_to_string
html.should eq %(<h3>Title!</h3>)
end
it "renders an H4" do
page = Page.new(nodes: [
Heading4.new(children: [
Text.new(content: "In Conclusion..."),
] of Child),
] of Child)
html = PageContent.new(page: page).render_to_string
html.should eq %(<h4>In Conclusion...</h4>)
end
it "renders an image" do
page = Page.new(nodes: [
Paragraph.new(children: [
Image.new(src: "image.png", originalWidth: 100, originalHeight: 100),
First step rendering a page The API responds with a bunch of paragraphs which the client converts into Paragraph objects. This turns the paragraphs in a PostResponse's Paragraph objects into the form needed to render them on a page. This includes converting flat list elements into list elements nested by a UL. And adding a limited markups along the way. The array of paragraphs is passed to a recursive function. The function takes the first paragraph and either wraps the (marked up) contents in a container tag (like Paragraph or Heading3), and then moves onto the next tag. If it finds a list, it starts parsing the next paragraphs as a list instead. Originally, this was implemented like so: ```crystal paragraph = paragraphs.shift if list? convert_list([paragraph] + paragraphs) end ``` However, passing the `paragraphs` after adding it to the already shifted `paragraph` creates a new object. This means `paragraphs` won't be mutated and once the list is parsed, it starts with the next element of the list. Instead, the element is `shift`ed inside each converter. ```crystal if paragraphs.first == list? convert_list(paragraphs) end def convert_list(paragraphs) paragraph = paragraphs.shift # ... end ``` When rendering, there is an Empty and Container object. These represent a kind of "null object" for both leafs and parent objects respectively. They should never actually render. Emptys are filtered out, and Containers are never created explicitly but this will make the types pass. IFrames are a bit of a special case. Each IFrame has custom data on it that this system would need to be aware of. For now, instead of trying to parse the seemingly large number of iframe variations and dealing with embedded iframe problems, this will just keep track of the source page URL and send the user there with a link.
2021-05-16 20:14:25 +02:00
] of Child)
] of Child)
html = PageContent.new(page: page).render_to_string
html.should eq stripped_html <<-HTML
<p>
<img src="https://cdn-images-1.medium.com/fit/c/100/100/image.png" width="100" height="100">
</p>
HTML
First step rendering a page The API responds with a bunch of paragraphs which the client converts into Paragraph objects. This turns the paragraphs in a PostResponse's Paragraph objects into the form needed to render them on a page. This includes converting flat list elements into list elements nested by a UL. And adding a limited markups along the way. The array of paragraphs is passed to a recursive function. The function takes the first paragraph and either wraps the (marked up) contents in a container tag (like Paragraph or Heading3), and then moves onto the next tag. If it finds a list, it starts parsing the next paragraphs as a list instead. Originally, this was implemented like so: ```crystal paragraph = paragraphs.shift if list? convert_list([paragraph] + paragraphs) end ``` However, passing the `paragraphs` after adding it to the already shifted `paragraph` creates a new object. This means `paragraphs` won't be mutated and once the list is parsed, it starts with the next element of the list. Instead, the element is `shift`ed inside each converter. ```crystal if paragraphs.first == list? convert_list(paragraphs) end def convert_list(paragraphs) paragraph = paragraphs.shift # ... end ``` When rendering, there is an Empty and Container object. These represent a kind of "null object" for both leafs and parent objects respectively. They should never actually render. Emptys are filtered out, and Containers are never created explicitly but this will make the types pass. IFrames are a bit of a special case. Each IFrame has custom data on it that this system would need to be aware of. For now, instead of trying to parse the seemingly large number of iframe variations and dealing with embedded iframe problems, this will just keep track of the source page URL and send the user there with a link.
2021-05-16 20:14:25 +02:00
end
it "renders an iframe container" do
page = Page.new(nodes: [
Paragraph.new(children: [
IFrame.new(href: "https://example.com"),
] of Child)
] of Child)
html = PageContent.new(page: page).render_to_string
html.should eq %(<p><div class="embedded"><a href="https://example.com">Click to visit embedded content</a></div></p>)
end
it "renders an ordered list" do
page = Page.new(nodes: [
OrderedList.new(children: [
ListItem.new(children: [Text.new("One")] of Child),
ListItem.new(children: [Text.new("Two")] of Child),
] of Child),
] of Child)
html = PageContent.new(page: page).render_to_string
html.should eq %(<ol><li>One</li><li>Two</li></ol>)
end
it "renders an preformatted text" do
page = Page.new(nodes: [
Paragraph.new(children: [
Text.new("Hello, world!"),
] of Child),
] of Child)
html = PageContent.new(page: page).render_to_string
html.should eq %(<p>Hello, world!</p>)
end
it "renders an preformatted text" do
page = Page.new(nodes: [
Preformatted.new(children: [
Text.new("New\nline"),
] of Child),
] of Child)
html = PageContent.new(page: page).render_to_string
html.should eq %(<pre>New\nline</pre>)
end
it "renders strong text" do
page = Page.new(nodes: [
Strong.new(children: [
Text.new("Oh yeah!"),
] of Child),
] of Child)
html = PageContent.new(page: page).render_to_string
html.should eq %(<strong>Oh yeah!</strong>)
end
it "renders an unordered list" do
page = Page.new(nodes: [
UnorderedList.new(children: [
ListItem.new(children: [Text.new("Apple")] of Child),
ListItem.new(children: [Text.new("Banana")] of Child),
] of Child),
] of Child)
html = PageContent.new(page: page).render_to_string
html.should eq %(<ul><li>Apple</li><li>Banana</li></ul>)
end
2021-07-04 23:37:45 +02:00
it "renders a user anchor" do
page = Page.new(nodes: [
UserAnchor.new(userId: "abc123", text: "Some User"),
] of Child)
html = PageContent.new(page: page).render_to_string
html.should eq %(<a href="https://medium.com/u/abc123">Some User</a>)
end
First step rendering a page The API responds with a bunch of paragraphs which the client converts into Paragraph objects. This turns the paragraphs in a PostResponse's Paragraph objects into the form needed to render them on a page. This includes converting flat list elements into list elements nested by a UL. And adding a limited markups along the way. The array of paragraphs is passed to a recursive function. The function takes the first paragraph and either wraps the (marked up) contents in a container tag (like Paragraph or Heading3), and then moves onto the next tag. If it finds a list, it starts parsing the next paragraphs as a list instead. Originally, this was implemented like so: ```crystal paragraph = paragraphs.shift if list? convert_list([paragraph] + paragraphs) end ``` However, passing the `paragraphs` after adding it to the already shifted `paragraph` creates a new object. This means `paragraphs` won't be mutated and once the list is parsed, it starts with the next element of the list. Instead, the element is `shift`ed inside each converter. ```crystal if paragraphs.first == list? convert_list(paragraphs) end def convert_list(paragraphs) paragraph = paragraphs.shift # ... end ``` When rendering, there is an Empty and Container object. These represent a kind of "null object" for both leafs and parent objects respectively. They should never actually render. Emptys are filtered out, and Containers are never created explicitly but this will make the types pass. IFrames are a bit of a special case. Each IFrame has custom data on it that this system would need to be aware of. For now, instead of trying to parse the seemingly large number of iframe variations and dealing with embedded iframe problems, this will just keep track of the source page URL and send the user there with a link.
2021-05-16 20:14:25 +02:00
end
def stripped_html(html : String)
html.gsub(/\n\s*/, "").strip
end