|
3 | 3 | * SPDX-License-Identifier: AGPL-3.0-or-later |
4 | 4 | */ |
5 | 5 |
|
6 | | -import { beforeAll, describe, expect, it } from 'vitest' |
| 6 | +import { describe, expect, it } from 'vitest' |
7 | 7 | import freemind from '../../plugins/freemind.js' |
8 | | -import util from '../../util.js' |
9 | | - |
10 | | -// freemind.toKm references the legacy global FilesMindMap.Util |
11 | | -beforeAll(() => { |
12 | | - global.FilesMindMap = { Util: util } |
13 | | -}) |
14 | 8 |
|
15 | 9 | describe('freemind plugin', () => { |
16 | 10 | it('has the correct name', () => { |
@@ -67,6 +61,54 @@ describe('freemind plugin', () => { |
67 | 61 | const obj = {} |
68 | 62 | freemind.processTopic({ TEXT: 'Unknown', icon: { BUILTIN: 'unknown-marker' } }, obj) |
69 | 63 | expect(obj.data.priority).toBeUndefined() |
| 64 | + expect(obj.data.text).toBe('Unknown') |
| 65 | + }) |
| 66 | + |
| 67 | + it('shows other icons as emoji before the text, or as the text of an icon-only node', () => { |
| 68 | + const withText = {} |
| 69 | + freemind.processTopic({ TEXT: 'Done', icon: [{ BUILTIN: 'button_ok' }, { BUILTIN: 'full-9' }, { BUILTIN: 'hourglass' }] }, withText) |
| 70 | + expect(withText.data.text).toBe('✅⌛ Done') |
| 71 | + expect(withText.data.priority).toBe(9) |
| 72 | + |
| 73 | + const iconOnly = {} |
| 74 | + freemind.processTopic({ TEXT: '', icon: { BUILTIN: 'ksmiletris' } }, iconOnly) |
| 75 | + expect(iconOnly.data.text).toBe('😊') |
| 76 | + }) |
| 77 | + |
| 78 | + it('takes over the font and colours of the node, but not of its line', () => { |
| 79 | + const obj = {} |
| 80 | + freemind.processTopic({ |
| 81 | + TEXT: 'RESULT', |
| 82 | + COLOR: '#ff0000', |
| 83 | + BACKGROUND_COLOR: '#99ff99', |
| 84 | + font: { BOLD: 'true', ITALIC: 'true', NAME: 'Monospaced', SIZE: '14' }, |
| 85 | + edge: { COLOR: '#00ff00', STYLE: 'bezier' }, |
| 86 | + }, obj) |
| 87 | + expect(obj.data).toEqual({ |
| 88 | + text: 'RESULT', |
| 89 | + color: '#ff0000', |
| 90 | + background: '#99ff99', |
| 91 | + 'font-weight': 'bold', |
| 92 | + 'font-style': 'italic', |
| 93 | + 'font-size': 14, |
| 94 | + 'font-family': 'monospace', |
| 95 | + }) |
| 96 | + }) |
| 97 | + |
| 98 | + it('leaves FreeMind\'s default font and unset styles to the editor', () => { |
| 99 | + const obj = {} |
| 100 | + freemind.processTopic({ TEXT: 'Plain', font: { NAME: 'SansSerif', SIZE: '12' } }, obj) |
| 101 | + expect(obj.data).toEqual({ text: 'Plain', 'font-size': 12 }) |
| 102 | + |
| 103 | + const named = {} |
| 104 | + freemind.processTopic({ TEXT: 'Named', font: { NAME: 'Arial', BOLD: 'false' } }, named) |
| 105 | + expect(named.data).toEqual({ text: 'Named', 'font-family': 'Arial' }) |
| 106 | + }) |
| 107 | + |
| 108 | + it('uses FreeMind\'s black text on a background without a text colour', () => { |
| 109 | + const obj = {} |
| 110 | + freemind.processTopic({ TEXT: 'Root', BACKGROUND_COLOR: '#99ff99' }, obj) |
| 111 | + expect(obj.data).toMatchObject({ background: '#99ff99', color: '#000000' }) |
70 | 112 | }) |
71 | 113 |
|
72 | 114 | it('processes a single child node', () => { |
@@ -103,6 +145,70 @@ describe('freemind plugin', () => { |
103 | 145 | expect(result.children[0].data.text).toBe('Child') |
104 | 146 | }) |
105 | 147 |
|
| 148 | + it('takes the text of a node without TEXT from its rich content', async () => { |
| 149 | + const xml = '<map version="1.1.0"><node><richcontent TYPE="NODE"><html><body>' |
| 150 | + + '<p>FreeMind</p><p> Template </p></body></html></richcontent>' |
| 151 | + + '<richcontent TYPE="NOTE"><html><body><p>A note</p></body></html></richcontent>' |
| 152 | + + '<node TEXT="Child"/></node></map>' |
| 153 | + const result = await freemind.decode(xml) |
| 154 | + expect(result.data.text).toBe('FreeMind\nTemplate') |
| 155 | + expect(result.children[0].data.text).toBe('Child') |
| 156 | + }) |
| 157 | + |
| 158 | + it('keeps line breaks in rich content but not the wrapping of its HTML', async () => { |
| 159 | + const xml = '<map version="1.0.1"><node><richcontent TYPE="NODE"><html><body>' |
| 160 | + + '<p align="center">\n FreeMind<br/><small>- free mind\n mapping software -</small>\n</p>' |
| 161 | + + '</body></html></richcontent></node></map>' |
| 162 | + const result = await freemind.decode(xml) |
| 163 | + expect(result.data.text).toBe('FreeMind\n- free mind mapping software -') |
| 164 | + }) |
| 165 | + |
| 166 | + it('takes the text of FreeMind 0.8 HTML in TEXT, with the chosen value of form fields', async () => { |
| 167 | + const xml = '<map version="0.8.0"><node TEXT="<html><img src="a.gif"><br>Complete Man">' |
| 168 | + + '<node TEXT="<html>Rating <select><option>--</option><option selected>++</option></select>"/>' |
| 169 | + + '<node TEXT="<html><select><option>--</option></select>"/>' |
| 170 | + + '</node></map>' |
| 171 | + const result = await freemind.decode(xml) |
| 172 | + expect(result.data.text).toBe('Complete Man') |
| 173 | + expect(result.children[0].data.text).toBe('Rating ++') |
| 174 | + expect(result.children[1].data.text).toBe('') |
| 175 | + }) |
| 176 | + |
| 177 | + it('names an image-only node after the image and links web images', async () => { |
| 178 | + const html = (img) => '<richcontent TYPE="NODE"><html><body>' + img + '</body></html></richcontent>' |
| 179 | + const xml = '<map version="1.0.1"><node TEXT="Root">' |
| 180 | + + '<node>' + html('<img src="http://example.com/images/menu/binah.gif"/>') + '</node>' |
| 181 | + + '<node>' + html('<img src="https://example.com/a.png" alt="Tree of life"/>') + '</node>' |
| 182 | + + '<node>' + html('<img src="file:/C:/Office/Bitmaps/ACBLENDS.GIF"/>') + '</node>' |
| 183 | + + '<node LINK="https://nextcloud.com">' + html('<img src="http://example.com/x%20y.gif"/>') + '</node>' |
| 184 | + + '<node TEXT="<html><img src="http://example.com/flag.gif"><br>Spirit Form"/>' |
| 185 | + + '</node></map>' |
| 186 | + const [web, alt, local, linked, withText] = (await freemind.decode(xml)).children.map((child) => child.data) |
| 187 | + expect(web).toMatchObject({ text: 'binah', hyperlink: 'http://example.com/images/menu/binah.gif' }) |
| 188 | + expect(alt).toMatchObject({ text: 'Tree of life', hyperlink: 'https://example.com/a.png' }) |
| 189 | + expect(local.text).toBe('ACBLENDS') |
| 190 | + expect(local.hyperlink).toBeUndefined() |
| 191 | + expect(linked).toMatchObject({ text: 'x y', hyperlink: 'https://nextcloud.com' }) |
| 192 | + expect(withText).toMatchObject({ text: 'Spirit Form', hyperlink: 'http://example.com/flag.gif' }) |
| 193 | + }) |
| 194 | + |
| 195 | + it('imports notes and folded branches', async () => { |
| 196 | + const xml = '<map version="1.0.1"><node TEXT="Root"><node TEXT="Folded" FOLDED="true">' |
| 197 | + + '<richcontent TYPE="NOTE"><html><body><p>First</p><p>Second</p></body></html></richcontent>' |
| 198 | + + '<node TEXT="Hidden"/></node></node></map>' |
| 199 | + const result = await freemind.decode(xml) |
| 200 | + expect(result.data.expandState).toBeUndefined() |
| 201 | + expect(result.children[0].data.expandState).toBe('collapse') |
| 202 | + expect(result.children[0].data.note).toBe('First\nSecond') |
| 203 | + expect(result.children[0].children[0].data.text).toBe('Hidden') |
| 204 | + }) |
| 205 | + |
| 206 | + it('rejects a file that is not a FreeMind map with a clear message', async () => { |
| 207 | + await expect(freemind.decode('<!DOCTYPE html><html><head><title>VIP.mm</title></head><body></body></html>')) |
| 208 | + .rejects.toThrow('This file is not a valid FreeMind mind map.') |
| 209 | + await expect(freemind.decode('<map version="1.0.1"></map>')).rejects.toThrow('not a valid FreeMind') |
| 210 | + }) |
| 211 | + |
106 | 212 | it('rejects on malformed input', async () => { |
107 | 213 | await expect(freemind.decode(null)).rejects.toBeDefined() |
108 | 214 | }) |
|
0 commit comments