diff --git a/admin/tests/form/MarkdownField.test.ts b/admin/tests/form/MarkdownField.test.ts
new file mode 100644
index 0000000..d2459e0
--- /dev/null
+++ b/admin/tests/form/MarkdownField.test.ts
@@ -0,0 +1,75 @@
+import { afterEach, beforeEach, describe, expect, it } from 'vitest'
+import { enableAutoUnmount, mount } from '@vue/test-utils'
+import type { FormField } from '../../src/api/types'
+import MarkdownField from '../../src/components/form/fields/MarkdownField.vue'
+import MLMarkdownField from '../../src/components/form/fields/MLMarkdownField.vue'
+import { resetState } from '../helpers'
+
+function field(type = 'markdown', name = 'body'): FormField {
+ return { name, label: 'Body', type } as FormField
+}
+
+beforeEach(() => {
+ resetState()
+})
+
+afterEach(() => {
+ document.body.innerHTML = ''
+})
+
+enableAutoUnmount(afterEach)
+
+describe('MarkdownField composition and sinks', () => {
+ it('edits source and previews interpolated text without v-html', async () => {
+ const wrapper = mount(MarkdownField, {
+ props: {
+ field: field(),
+ modelValue: '# Hello',
+ controlId: 'f-body',
+ },
+ attachTo: document.body,
+ })
+ expect(wrapper.find('textarea').element.value).toBe('# Hello')
+ await wrapper.find('textarea').setValue('# Changed')
+ expect(wrapper.emitted('update:modelValue')?.at(-1)).toEqual(['# Changed'])
+ await wrapper.setProps({ modelValue: '# Changed' })
+ await wrapper.find('[data-markdown-preview]').trigger('click')
+ expect(wrapper.find('[data-markdown-preview-pane]').text()).toBe('# Changed')
+ expect(wrapper.find('h1').exists()).toBe(false)
+ })
+
+ it('does not execute script, iframe, or event-handler HTML', async () => {
+ const wrapper = mount(MarkdownField, {
+ props: {
+ field: field(),
+ modelValue:
+ '
',
+ controlId: 'f-body',
+ },
+ attachTo: document.body,
+ })
+ await wrapper.find('[data-markdown-preview]').trigger('click')
+ expect(wrapper.find('script').exists()).toBe(false)
+ expect(wrapper.find('iframe').exists()).toBe(false)
+ expect(wrapper.find('img').exists()).toBe(false)
+ expect((window as unknown as { __md_xss?: number }).__md_xss).toBeUndefined()
+ expect(wrapper.html()).not.toMatch(/v-html|innerHTML/)
+ })
+
+ it('composes inside mlmarkdown without a raw-HTML sink', async () => {
+ const wrapper = mount(MLMarkdownField, {
+ props: {
+ field: field('mlmarkdown', 'body'),
+ modelValue: { en: '# Hi', pl: '' },
+ controlId: 'f-body',
+ },
+ attachTo: document.body,
+ })
+ expect(wrapper.findComponent(MarkdownField).exists()).toBe(true)
+ await wrapper.find('[data-ml-locale]').setValue('pl')
+ await wrapper.find('[data-markdown-preview]').trigger('click')
+ expect(wrapper.find('script').exists()).toBe(false)
+ expect((window as unknown as { __md_xss?: number }).__md_xss).toBeUndefined()
+ expect(wrapper.find('[data-markdown-preview-pane]').text()).toContain('", "