-
-
Notifications
You must be signed in to change notification settings - Fork 4.8k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Android: Add support for renderer plugins (#10135)
- Loading branch information
1 parent
44e8950
commit e92f89d
Showing
19 changed files
with
1,106 additions
and
357 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
161 changes: 161 additions & 0 deletions
161
packages/app-mobile/components/NoteBodyViewer/bundledJs/Renderer.test.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,161 @@ | ||
import Setting from '@joplin/lib/models/Setting'; | ||
import Renderer, { RendererSettings, RendererSetupOptions } from './Renderer'; | ||
import shim from '@joplin/lib/shim'; | ||
import { MarkupLanguage } from '@joplin/renderer'; | ||
|
||
const defaultRendererSettings: RendererSettings = { | ||
theme: JSON.stringify({ cacheKey: 'test' }), | ||
onResourceLoaded: ()=>{}, | ||
highlightedKeywords: [], | ||
resources: {}, | ||
codeTheme: 'atom-one-light.css', | ||
noteHash: '', | ||
initialScroll: 0, | ||
|
||
createEditPopupSyntax: '', | ||
destroyEditPopupSyntax: '', | ||
|
||
pluginSettings: {}, | ||
requestPluginSetting: ()=>{}, | ||
}; | ||
|
||
const makeRenderer = (options: Partial<RendererSetupOptions>) => { | ||
const defaultSetupOptions: RendererSetupOptions = { | ||
settings: { | ||
safeMode: false, | ||
tempDir: Setting.value('tempDir'), | ||
resourceDir: Setting.value('resourceDir'), | ||
resourceDownloadMode: 'auto', | ||
}, | ||
fsDriver: shim.fsDriver(), | ||
pluginOptions: {}, | ||
}; | ||
return new Renderer({ ...options, ...defaultSetupOptions }); | ||
}; | ||
|
||
const getRenderedContent = () => { | ||
return document.querySelector('#joplin-container-content > #rendered-md'); | ||
}; | ||
|
||
describe('Renderer', () => { | ||
beforeEach(() => { | ||
const contentContainer = document.createElement('div'); | ||
contentContainer.id = 'joplin-container-content'; | ||
document.body.appendChild(contentContainer); | ||
|
||
const pluginAssetsContainer = document.createElement('div'); | ||
pluginAssetsContainer.id = 'joplin-container-pluginAssetsContainer'; | ||
document.body.appendChild(pluginAssetsContainer); | ||
}); | ||
|
||
afterEach(() => { | ||
document.querySelector('#joplin-container-content')?.remove(); | ||
document.querySelector('#joplin-container-pluginAssetsContainer')?.remove(); | ||
}); | ||
|
||
test('should support rendering markdown', async () => { | ||
const renderer = makeRenderer({}); | ||
await renderer.rerender( | ||
{ language: MarkupLanguage.Markdown, markup: '**test**' }, | ||
defaultRendererSettings, | ||
); | ||
|
||
expect(getRenderedContent().innerHTML.trim()).toBe('<p><strong>test</strong></p>'); | ||
|
||
await renderer.rerender( | ||
{ language: MarkupLanguage.Markdown, markup: '*test*' }, | ||
defaultRendererSettings, | ||
); | ||
expect(getRenderedContent().innerHTML.trim()).toBe('<p><em>test</em></p>'); | ||
}); | ||
|
||
test('should support adding and removing plugin scripts', async () => { | ||
const renderer = makeRenderer({}); | ||
await renderer.setExtraContentScriptsAndRerender([ | ||
{ | ||
id: 'test', | ||
js: ` | ||
((context) => { | ||
return { | ||
plugin: (markdownIt) => { | ||
markdownIt.renderer.rules.fence = (tokens, idx) => { | ||
return '<div id="test">Test from ' + context.pluginId + '</div>'; | ||
}; | ||
}, | ||
}; | ||
}) | ||
`, | ||
assetPath: Setting.value('tempDir'), | ||
pluginId: 'com.example.test-plugin', | ||
}, | ||
]); | ||
await renderer.rerender( | ||
{ language: MarkupLanguage.Markdown, markup: '```\ntest\n```' }, | ||
defaultRendererSettings, | ||
); | ||
expect(getRenderedContent().innerHTML.trim()).toBe('<div id="test">Test from com.example.test-plugin</div>'); | ||
|
||
// Should support removing plugin scripts | ||
await renderer.setExtraContentScriptsAndRerender([]); | ||
await renderer.rerender( | ||
{ language: MarkupLanguage.Markdown, markup: '```\ntest\n```' }, | ||
defaultRendererSettings, | ||
); | ||
expect(getRenderedContent().innerHTML.trim()).not.toContain('com.example.test-plugin'); | ||
expect(getRenderedContent().querySelectorAll('pre.joplin-source')).toHaveLength(1); | ||
}); | ||
|
||
test('should call .requestPluginSetting when a setting is missing', async () => { | ||
const renderer = makeRenderer({}); | ||
|
||
const requestPluginSetting = jest.fn(); | ||
const rerender = (pluginSettings: Record<string, any>) => { | ||
return renderer.rerender( | ||
{ language: MarkupLanguage.Markdown, markup: '```\ntest\n```' }, | ||
{ ...defaultRendererSettings, pluginSettings, requestPluginSetting }, | ||
); | ||
}; | ||
|
||
await rerender({}); | ||
expect(requestPluginSetting).toHaveBeenCalledTimes(0); | ||
|
||
const pluginId = 'com.example.test-plugin'; | ||
await renderer.setExtraContentScriptsAndRerender([ | ||
{ | ||
id: 'test-content-script', | ||
js: ` | ||
(() => { | ||
return { | ||
plugin: (markdownIt, options) => { | ||
const settingValue = options.settingValue('setting'); | ||
markdownIt.renderer.rules.fence = (tokens, idx) => { | ||
return '<div id="setting-value">Setting value: ' + settingValue + '</div>'; | ||
}; | ||
}, | ||
}; | ||
}) | ||
`, | ||
assetPath: Setting.value('tempDir'), | ||
pluginId, | ||
}, | ||
]); | ||
|
||
// Should call .requestPluginSetting for missing settings | ||
expect(requestPluginSetting).toHaveBeenCalledTimes(1); | ||
await rerender({}); | ||
expect(requestPluginSetting).toHaveBeenCalledTimes(2); | ||
expect(requestPluginSetting).toHaveBeenLastCalledWith('com.example.test-plugin', 'setting'); | ||
|
||
// Should still render | ||
expect(getRenderedContent().querySelector('#setting-value').innerHTML).toBe('Setting value: undefined'); | ||
|
||
// Should expect only namespaced plugin settings | ||
await rerender({ 'setting': 'test' }); | ||
expect(requestPluginSetting).toHaveBeenCalledTimes(3); | ||
|
||
// Should not request plugin settings when all settings are present. | ||
await rerender({ [`${pluginId}.setting`]: 'test' }); | ||
expect(requestPluginSetting).toHaveBeenCalledTimes(3); | ||
expect(getRenderedContent().querySelector('#setting-value').innerHTML).toBe('Setting value: test'); | ||
}); | ||
}); |
Oops, something went wrong.