.
83 | They are also obfuscated so that email harvesting spam robots hopefully won't get them.
84 |
85 |
86 | Lists
87 | =====
88 |
89 | * This is a bulleted list
90 | * Great for shopping lists
91 | - You can also use hyphens
92 | + Or plus symbols
93 |
94 | The above is an "unordered" list. Now, on for a bit of order.
95 |
96 | 1. Numbered lists are also easy
97 | 2. Just start with a number
98 | 3738762. However, the actual number doesn't matter when converted to HTML.
99 | 1. This will still show up as 4.
100 |
101 | You might want a few advanced lists:
102 |
103 | - This top-level list is wrapped in paragraph tags
104 | - This generates an extra space between each top-level item.
105 |
106 | - You do it by adding a blank line
107 |
108 | - This nested list also has blank lines between the list items.
109 |
110 | - How to create nested lists
111 | 1. Start your regular list
112 | 2. Indent nested lists with two spaces
113 | 3. Further nesting means you should indent with two more spaces
114 | * This line is indented with four spaces.
115 |
116 | - List items can be quite lengthy. You can keep typing and either continue
117 | them on the next line with no indentation.
118 |
119 | - Alternately, if that looks ugly, you can also
120 | indent the next line a bit for a prettier look.
121 |
122 | - You can put large blocks of text in your list by just indenting with two spaces.
123 |
124 | This is formatted the same as code, but you can inspect the HTML and find that it's just wrapped in a \`\` tag and *won't* be shown as preformatted text.
125 |
126 | You can keep adding more and more paragraphs to a single list item by adding the traditional blank line and then keep on indenting the paragraphs with two spaces.
127 |
128 | You really only need to indent the first line,
129 | but that looks ugly.
130 |
131 | - Lists support blockquotes
132 |
133 | > Just like this example here. By the way, you can
134 | > nest lists inside blockquotes!
135 | > - Fantastic!
136 |
137 | - Lists support preformatted text
138 |
139 | You just need to indent an additional four spaces.
140 |
141 |
142 | Even More
143 | =========
144 |
145 | Horizontal Rule
146 | ---------------
147 |
148 | If you need a horizontal rule you just need to put at least three hyphens, asterisks, or underscores on a line by themselves. You can also even put spaces between the characters.
149 |
150 | ---
151 | ****************************
152 | _ _ _ _ _ _ _
153 |
154 | Those three all produced horizontal lines. Keep in mind that three hyphens under any text turns that text into a heading, so add a blank like if you use hyphens.
155 |
156 | Images
157 | ------
158 |
159 | Images work exactly like links, but they have exclamation points in front. They work with references and titles too.
160 |
161 |  and ![Happy].
162 |
163 | [Happy]: https://wpclipart.com/smiley/happy/simple_colors/smiley_face_simple_green_small.png ("Smiley face")
164 |
165 |
166 | Inline HTML
167 | -----------
168 |
169 | If markdown is too limiting, you can just insert your own crazy HTML. Span-level HTML can *still* use markdown. Block level elements must be separated from text by a blank line and must not have any spaces before the opening and closing HTML.
170 |
171 |
172 | It is a pity, but markdown does **not** work in here for most markdown parsers.
173 | [Marked] handles it pretty well.
174 |
175 | `;
176 |
177 | const styles = StyleSheet.create({
178 | em: {
179 | fontSize: 12,
180 | },
181 | strong: {
182 | fontSize: 12,
183 | },
184 | strikethrough: {
185 | fontSize: 12,
186 | },
187 | text: {
188 | fontSize: 12,
189 | },
190 | paragraph: {
191 | borderWidth: 1,
192 | },
193 | link: {
194 | fontSize: 12,
195 | },
196 | blockquote: {
197 | borderWidth: 1,
198 | },
199 | h1: {
200 | fontSize: 12,
201 | },
202 | h2: {
203 | fontSize: 12,
204 | },
205 | h3: {
206 | fontSize: 12,
207 | },
208 | h4: {
209 | fontSize: 12,
210 | },
211 | h5: {
212 | fontSize: 12,
213 | },
214 | h6: {
215 | fontSize: 12,
216 | },
217 | codespan: {
218 | fontSize: 12,
219 | },
220 | code: {
221 | borderWidth: 1,
222 | },
223 | hr: {
224 | borderWidth: 1,
225 | },
226 | list: {
227 | borderWidth: 1,
228 | },
229 | li: {
230 | fontSize: 12,
231 | },
232 | image: {
233 | width: "100%",
234 | },
235 | table: {
236 | borderWidth: 1,
237 | },
238 | tableRow: {
239 | borderWidth: 1,
240 | },
241 | tableCell: {
242 | borderWidth: 1,
243 | },
244 | });
245 |
246 | const theme: UserTheme = {
247 | colors: {
248 | background: "#ffffff",
249 | link: "#58a6ff",
250 | border: "#d0d7de",
251 | code: "#161b22",
252 | text: "#ffffff",
253 | },
254 | spacing: {
255 | xs: 3,
256 | s: 6,
257 | m: 9,
258 | l: 18,
259 | },
260 | };
261 |
262 | describe("Perf test", () => {
263 | it("Renders markdown", async () => {
264 | const scenario = async () => {
265 | await screen.queryByText("Markdown Quick Reference");
266 | await screen.queryByText("Inline HTML");
267 | await screen.queryByText(
268 | "If markdown is too limiting, you can just insert your own crazy HTML. Span-level HTML can *still* use markdown. Block level elements must be separated from text by a blank line and must not have any spaces before the opening and closing HTML.",
269 | );
270 | };
271 | measureRenders(
272 | ,
273 | {
274 | scenario,
275 | },
276 | );
277 | });
278 | });
279 |
--------------------------------------------------------------------------------
/src/lib/__tests__/Markdown.spec.tsx:
--------------------------------------------------------------------------------
1 | import React, { type ReactNode } from "react";
2 | import { render, screen, waitFor } from "@testing-library/react-native";
3 | import { Text, type TextStyle } from "react-native";
4 | import Markdown from "../Markdown";
5 | import Renderer from "../Renderer";
6 | import type { RendererInterface } from "../types";
7 | import { Tokenizer, type Tokens } from "marked";
8 |
9 | // https://www.markdownguide.org/basic-syntax/#headings
10 | describe("Headings", () => {
11 | it("Heading level 1", () => {
12 | const r = render();
13 | expect(screen.queryByText("Heading level 1")).toBeTruthy();
14 | const tree = r.toJSON();
15 | expect(tree).toMatchSnapshot();
16 | });
17 | it("Heading level 2", () => {
18 | const r = render();
19 | expect(screen.queryByText("Heading level 2")).toBeTruthy();
20 | const tree = r.toJSON();
21 | expect(tree).toMatchSnapshot();
22 | });
23 | it("Heading level 3", () => {
24 | const r = render();
25 | expect(screen.queryByText("Heading level 3")).toBeTruthy();
26 | const tree = r.toJSON();
27 | expect(tree).toMatchSnapshot();
28 | });
29 | it("Heading level 4", () => {
30 | const r = render();
31 | expect(screen.queryByText("Heading level 4")).toBeTruthy();
32 | const tree = r.toJSON();
33 | expect(tree).toMatchSnapshot();
34 | });
35 | it("Heading level 5", () => {
36 | const r = render();
37 | expect(screen.queryByText("Heading level 5")).toBeTruthy();
38 | const tree = r.toJSON();
39 | expect(tree).toMatchSnapshot();
40 | });
41 | it("Heading level 6", () => {
42 | const r = render();
43 | expect(screen.queryByText("Heading level 6")).toBeTruthy();
44 | const tree = r.toJSON();
45 | expect(tree).toMatchSnapshot();
46 | });
47 | it("Alternate Syntax: Heading level 1", () => {
48 | const r = render();
49 | expect(screen.queryByText("Heading level 1")).toBeTruthy();
50 | const tree = r.toJSON();
51 | expect(tree).toMatchSnapshot();
52 | });
53 | it("Alternate Syntax: Heading level 2", () => {
54 | const r = render();
55 | expect(screen.queryByText("Heading level 2")).toBeTruthy();
56 | const tree = r.toJSON();
57 | expect(tree).toMatchSnapshot();
58 | });
59 | it("Best Practice", () => {
60 | const r = render(
61 | ,
66 | );
67 | expect(screen.queryByText("Heading")).toBeTruthy();
68 | expect(
69 | screen.queryByText("Try to put a blank line before..."),
70 | ).toBeTruthy();
71 | expect(screen.queryByText("...and after a heading.")).toBeTruthy();
72 | const tree = r.toJSON();
73 | expect(tree).toMatchSnapshot();
74 | });
75 | it("Heading with text emphasis", () => {
76 | const r = render();
77 | expect(screen.queryByText("Heading level 2")).toBeTruthy();
78 | const tree = r.toJSON();
79 | expect(tree).toMatchSnapshot();
80 | });
81 | });
82 |
83 | // https://www.markdownguide.org/basic-syntax/#paragraphs-1
84 | describe("Paragraphs", () => {
85 | it("Paragraph", () => {
86 | const r = render(
87 | ,
92 | );
93 | expect(screen.queryByText("I really like using Markdown.")).toBeTruthy();
94 | expect(
95 | screen.queryByText(
96 | "I think I'll use it to format all of my documents from now on.",
97 | ),
98 | ).toBeTruthy();
99 | const tree = r.toJSON();
100 | expect(tree).toMatchSnapshot();
101 | });
102 | it("Paragraph with Image", async () => {
103 | const r = render(
104 | ,
109 | );
110 | await waitFor(() => {
111 | expect(
112 | screen.queryByText(
113 | "Here, I'll guide you through sending desktop notifications to offline users when they have new chat messages.",
114 | ),
115 | ).toBeTruthy();
116 | expect(
117 | screen.queryAllByTestId("react-native-marked-md-image"),
118 | ).toBeDefined();
119 | const tree = r.toJSON();
120 | expect(tree).toMatchSnapshot();
121 | });
122 | });
123 | });
124 |
125 | describe("Line Breaks", () => {
126 | it("Trailing New Line Character", () => {
127 | const r = render(
128 | ,
131 | );
132 | expect(
133 | screen.queryByText(
134 | "First line with a backslash after. And the next line.",
135 | ),
136 | ).toBeTruthy();
137 | const tree = r.toJSON();
138 | expect(tree).toMatchSnapshot();
139 | });
140 |
141 | it("Trailing slash", () => {
142 | const r = render(
143 | ,
147 | );
148 | expect(
149 | screen.queryByText("First line with a backslash after."),
150 | ).toBeTruthy();
151 | expect(screen.queryByText("And the next line.")).toBeTruthy();
152 | const tree = r.toJSON();
153 | expect(tree).toMatchSnapshot();
154 | });
155 | });
156 |
157 | // https://www.markdownguide.org/basic-syntax/#emphasis
158 | describe("Emphasis", () => {
159 | it("Bold", () => {
160 | const r = render();
161 | expect(screen.queryByText("is")).toBeTruthy();
162 | const tree = r.toJSON();
163 | expect(tree).toMatchSnapshot();
164 | });
165 | it("Italic", () => {
166 | const r = render();
167 | expect(screen.queryByText("cat")).toBeTruthy();
168 | const tree = r.toJSON();
169 | expect(tree).toMatchSnapshot();
170 | });
171 | it("Strikethrough", () => {
172 | const r = render();
173 | expect(screen.queryByText("cat")).toBeTruthy();
174 | const tree = r.toJSON();
175 | expect(tree).toMatchSnapshot();
176 | });
177 | it("Bold and Italic", () => {
178 | const r = render(
179 | ,
180 | );
181 | expect(screen.queryByText("very")).toBeTruthy();
182 | const tree = r.toJSON();
183 | expect(tree).toMatchSnapshot();
184 | });
185 | });
186 |
187 | // https://www.markdownguide.org/basic-syntax/#blockquotes-1
188 | describe("Blockquotes", () => {
189 | it("Blockquote", () => {
190 | const r = render(
191 | Dorothy followed her through many of the beautiful rooms in her castle."
194 | }
195 | />,
196 | );
197 | expect(
198 | screen.queryByText(
199 | "Dorothy followed her through many of the beautiful rooms in her castle.",
200 | ),
201 | ).toBeTruthy();
202 | const tree = r.toJSON();
203 | expect(tree).toMatchSnapshot();
204 | });
205 | it("Blockquotes with Multiple Paragraphs", () => {
206 | const r = render(
207 | Dorothy followed her through many of the beautiful rooms in her castle.\n>\n> The Witch bade her clean the pots and kettles and sweep the floor and keep the fire fed with wood."
210 | }
211 | />,
212 | );
213 | expect(
214 | screen.queryByText(
215 | "Dorothy followed her through many of the beautiful rooms in her castle.",
216 | ),
217 | ).toBeTruthy();
218 | expect(
219 | screen.queryByText(
220 | "The Witch bade her clean the pots and kettles and sweep the floor and keep the fire fed with wood.",
221 | ),
222 | ).toBeTruthy();
223 | const tree = r.toJSON();
224 | expect(tree).toMatchSnapshot();
225 | });
226 | it("Nested Blockquotes", () => {
227 | const r = render(
228 | Dorothy followed her through many of the beautiful rooms in her castle.\n>\n\n>> The Witch bade her clean the pots and kettles and sweep the floor and keep the fire fed with wood."
231 | }
232 | />,
233 | );
234 | expect(
235 | screen.queryByText(
236 | "Dorothy followed her through many of the beautiful rooms in her castle.",
237 | ),
238 | ).toBeTruthy();
239 | expect(
240 | screen.queryByText(
241 | "The Witch bade her clean the pots and kettles and sweep the floor and keep the fire fed with wood.",
242 | ),
243 | ).toBeTruthy();
244 | const tree = r.toJSON();
245 | expect(tree).toMatchSnapshot();
246 | });
247 | it("Blockquotes with Other Elements", () => {
248 | const r = render(
249 | #### The quarterly results look great!\n>\n> - Revenue was off the chart.\n> - Profits were higher than ever.\n>\n> *Everything* is going according to **plan**."
252 | }
253 | />,
254 | );
255 | expect(
256 | screen.queryByText("The quarterly results look great!"),
257 | ).toBeTruthy();
258 | expect(screen.queryByText("Revenue was off the chart.")).toBeTruthy();
259 | expect(screen.queryByText("Profits were higher than ever.")).toBeTruthy();
260 | expect(screen.queryByText("Everything")).toBeTruthy();
261 | expect(screen.queryByText("is going according to")).toBeTruthy();
262 | expect(screen.queryByText("plan")).toBeTruthy();
263 | const tree = r.toJSON();
264 | expect(tree).toMatchSnapshot();
265 | });
266 | });
267 |
268 | // https://www.markdownguide.org/basic-syntax/#lists-1
269 | describe("Lists", () => {
270 | it("Ordered Lists", () => {
271 | const r = render(
272 | ,
277 | );
278 | expect(screen.queryByText("First item")).toBeTruthy();
279 | expect(screen.queryByText("Second item")).toBeTruthy();
280 | expect(screen.queryByText("Third item")).toBeTruthy();
281 | expect(screen.queryByText("Indented item1")).toBeTruthy();
282 | expect(screen.queryByText("Indented item2")).toBeTruthy();
283 | expect(screen.queryByText("Fourth item")).toBeTruthy();
284 | const tree = r.toJSON();
285 | expect(tree).toMatchSnapshot();
286 | });
287 | it("Ordered Lists: With Start Offset", () => {
288 | const r = render();
289 | expect(screen.queryByText("foo")).toBeTruthy();
290 | expect(screen.queryByText("bar")).toBeTruthy();
291 | expect(screen.queryByText("baz")).toBeTruthy();
292 | expect(screen.queryByText("57.")).toBeTruthy();
293 | expect(screen.queryByText("58.")).toBeTruthy();
294 | expect(screen.queryByText("59.")).toBeTruthy();
295 | const tree = r.toJSON();
296 | expect(tree).toMatchSnapshot();
297 | });
298 | it("Unordered Lists", () => {
299 | const r = render(
300 | ,
305 | );
306 | expect(screen.queryByText("First item")).toBeTruthy();
307 | expect(screen.queryByText("Second item")).toBeTruthy();
308 | expect(screen.queryByText("Third item")).toBeTruthy();
309 | expect(screen.queryByText("Indented item1")).toBeTruthy();
310 | expect(screen.queryByText("Indented item2")).toBeTruthy();
311 | expect(screen.queryByText("Fourth item")).toBeTruthy();
312 | const tree = r.toJSON();
313 | expect(tree).toMatchSnapshot();
314 | });
315 | it("Elements in Lists: Paragraphs", () => {
316 | const r = render(
317 | ,
322 | );
323 | expect(screen.queryByText("This is the first list item.")).toBeTruthy();
324 | expect(screen.queryByText("Here's the second list item.")).toBeTruthy();
325 | expect(
326 | screen.queryByText(
327 | "I need to add another paragraph below the second list item.",
328 | ),
329 | ).toBeTruthy();
330 | expect(screen.queryByText("And here's the third list item.")).toBeTruthy();
331 | const tree = r.toJSON();
332 | expect(tree).toMatchSnapshot();
333 | });
334 | it("Elements in Lists: Blockquotes", () => {
335 | const r = render(
336 | A blockquote would look great below the second list item.\n\n- And here's the third list item."
339 | }
340 | />,
341 | );
342 | expect(screen.queryByText("This is the first list item.")).toBeTruthy();
343 | expect(screen.queryByText("Here's the second list item.")).toBeTruthy();
344 | expect(
345 | screen.queryByText(
346 | "A blockquote would look great below the second list item.",
347 | ),
348 | ).toBeTruthy();
349 | expect(screen.queryByText("And here's the third list item.")).toBeTruthy();
350 | const tree = r.toJSON();
351 | expect(tree).toMatchSnapshot();
352 | });
353 | it("Elements in Lists: Code Blocks", () => {
354 | const r = render(
355 | \n \n \n