Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
111 changes: 111 additions & 0 deletions playground/components/global/drupal-ce-container.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
/**
* DrupalCeContainer Component
*
* A component that serves as a container for rendering Drupal custom elements.
* Supports two modes of operation:
*
* 1. Slot Mode - Using Vue slots for content
* 2. JSON Mode - Using content prop with array of elements
*
* @example Slot Mode Usage:
* ```vue
* <DrupalCeContainer tag="div" class="foo">
* <drupal-markup>Cell content before embed</drupal-markup>
* <drupal-media id="123">Some example embedded element.</drupal-media>
* <drupal-markup>Cell content after embed</drupal-markup>
* </DrupalCeContainer>
* ```
*
* @example JSON Mode Usage:
* ```vue
* <DrupalCeContainer
* tag="div"
* class="foo"
* :content="[
* {
* 'element': 'drupal-markup',
* 'content': 'Cell content before embed'
* },
* {
* 'element': 'drupal-media',
* 'id': '123',
* 'content': 'Some example embedded element.'
* },
* {
* 'element': 'drupal-markup',
* 'content': 'Cell content after embed'
* }
* ]"
* />
* ```
*/

import { h, defineComponent } from 'vue';
import type { VNode } from 'vue';
// useDrupalCe is auto-imported by Nuxt

export default defineComponent({
name: 'DrupalCeContainer',

props: {
/**
* HTML tag to use for the container
* @default 'div'
*/
tag: {
type: String,
default: 'div'
},

/**
* Array of custom elements to render (for JSON mode)
* Each element should have an 'element' property defining the component type
* @optional
*/
content: {
type: Array,
default: null
}
},

// @todo: Remove once issue #332 is fixed.
// We manually inherit attrs below.
inheritAttrs: false,

/**
* @slot default - Content slot for custom elements in Slot mode
*/

render(): VNode {
// useDrupalCe is auto-imported by Nuxt
const { renderCustomElements } = useDrupalCe();

// Determine which mode we're in - slot mode or JSON mode
const isJsonMode = Array.isArray(this.content);

// Get all props except 'tag', 'content', and 'element' to pass to the container
const containerProps = { ...this.$attrs };

// Do not render the "element" prop passed by renderCustomElements()
// @todo: Remove once issue #332 is fixed.
if ('element' in containerProps) {
delete containerProps.element;
}

let children;

if (isJsonMode && this.content) {
// JSON mode - render each element in the content array
children = this.content.map(item => renderCustomElements(item));
} else if (this.$slots.default) {
// Slot mode - use the default slot content
children = this.$slots.default();
} else {
// No content
children = [];
}

// Render the container with the appropriate tag and children.
return h(this.tag, containerProps, children);
}
});
251 changes: 251 additions & 0 deletions test/unit/components/drupal-ce-container.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,251 @@
// @vitest-environment nuxt
import { describe, it, expect } from 'vitest'
import { mountSuspended } from '@nuxt/test-utils/runtime'
import { defineComponent } from 'vue'
import { useNuxtApp } from "#imports"

describe('DrupalCeContainer', () => {
// Register test components
const DrupalMedia = defineComponent({
name: 'DrupalMedia',
inheritAttrs: false,
props: {
id: String,
content: String
},
template: '<div class="drupal-media" :data-id="id">{{ content }}</div>'
})

// Register components globally
const app = useNuxtApp()
app.vueApp.component('DrupalMedia', DrupalMedia)
// Note: DrupalMarkup is already available in the playground

// Helper function to normalize HTML by removing whitespace between elements
const normalizeHtml = (html) => {
return html
.replace(/>\s+</g, '><') // Remove whitespace between tags
.replace(/\s+/g, ' ') // Replace multiple spaces with single space
.trim(); // Trim leading/trailing whitespace
}

it('renders with slot-based content', async () => {
const wrapper = await mountSuspended(defineComponent({
template: `
<DrupalCeContainer tag="section" class="test-container">
<template #default>
<drupal-markup>Cell content before embed</drupal-markup>
<drupal-media id="123" content="Some example embedded element."></drupal-media>
<drupal-markup>Cell content after embed</drupal-markup>
</template>
</DrupalCeContainer>
`
}))

const html = normalizeHtml(wrapper.html())

// Create a well-formatted expected HTML string for easy reading in the IDE
const expectedHtml = normalizeHtml(`
<section class="test-container">Cell content before embed<div class="drupal-media" data-id="123">Some example embedded element.</div>Cell content after embed</section>
`)

// Assert the exact HTML structure
expect(html).toEqual(expectedHtml)
})

it('renders with JSON-based content', async () => {
const { renderCustomElements } = useDrupalCe()

const wrapper = await mountSuspended(defineComponent({
setup() {
return {
component: renderCustomElements({
element: 'drupal-ce-container',
tag: 'section',
class: 'test-container',
content: [
{
'element': 'drupal-markup',
'content': 'Cell content before embed'
},
{
'element': 'drupal-media',
'id': "123",
'content': 'Some example embedded element.'
},
{
'element': 'drupal-markup',
'content': 'Cell content after embed'
}
]
})
}
},
template: '<component :is="component" />'
}))

const html = normalizeHtml(wrapper.html())

// Create a well-formatted expected HTML string for easy reading in the IDE
// In JSON mode, DrupalMarkup components do add a wrapper div
const expectedHtml = normalizeHtml(`
<section class="test-container">
<div style="display: contents;">Cell content before embed</div>
<div class="drupal-media" data-id="123">Some example embedded element.</div>
<div style="display: contents;">Cell content after embed</div>
</section>
`)

// Assert the exact HTML structure
expect(html).toEqual(expectedHtml)
})

it('respects the default tag prop value', async () => {
const wrapper = await mountSuspended(defineComponent({
template: `
<DrupalCeContainer class="test-container">
<template #default>
<drupal-markup>Test content</drupal-markup>
</template>
</DrupalCeContainer>
`
}))

const html = normalizeHtml(wrapper.html())

// Create a well-formatted expected HTML string for easy reading in the IDE
const expectedHtml = normalizeHtml(`
<div class="test-container">Test content</div>
`)

// Assert the exact HTML structure
expect(html).toEqual(expectedHtml)
})

it('works with empty content', async () => {
const wrapper = await mountSuspended(defineComponent({
template: `
<DrupalCeContainer class="empty-container">
</DrupalCeContainer>
`
}))

const html = normalizeHtml(wrapper.html())

// Create a well-formatted expected HTML string for easy reading in the IDE
const expectedHtml = normalizeHtml(`
<div class="empty-container"></div>
`)

// Assert the exact HTML structure
expect(html).toEqual(expectedHtml)
})

it('renders table with embedded content using slot syntax', async () => {
const wrapper = await mountSuspended(defineComponent({
template: `
<DrupalCeContainer tag="table">
<DrupalCeContainer tag="tbody">
<DrupalCeContainer tag="tr">
<DrupalCeContainer tag="td">
<drupal-markup>First column</drupal-markup>
</DrupalCeContainer>
<DrupalCeContainer tag="td">
<drupal-markup><h2>Media heading</h2></drupal-markup>
<drupal-media id="123" content="Media content"></drupal-media>
</DrupalCeContainer>
</DrupalCeContainer>
</DrupalCeContainer>
</DrupalCeContainer>
`
}))

const html = normalizeHtml(wrapper.html())

// Create a well-formatted expected HTML string for easy reading in the IDE
const expectedHtml = normalizeHtml(`
<table><tbody><tr><td>First column</td><td><h2>Media heading</h2><div class="drupal-media" data-id="123">Media content</div></td></tr></tbody></table>
`)

// Assert the exact HTML structure
expect(html).toEqual(expectedHtml)
})

it('renders table with embedded content using JSON syntax', async () => {
const { renderCustomElements } = useDrupalCe()

const wrapper = await mountSuspended(defineComponent({
setup() {
return {
component: renderCustomElements({
element: 'drupal-ce-container',
tag: 'table',
content: [
{
element: 'drupal-ce-container',
tag: 'tbody',
content: [
{
element: 'drupal-ce-container',
tag: 'tr',
content: [
{
element: 'drupal-ce-container',
tag: 'td',
content: [
{
element: 'drupal-markup',
content: 'First column'
}
]
},
{
element: 'drupal-ce-container',
tag: 'td',
content: [
{
element: 'drupal-markup',
content: '<h2>Media heading</h2>'
},
{
element: 'drupal-media',
id: '123',
content: 'Media content'
}
]
}
]
}
]
}
]
})
}
},
template: '<component :is="component" />'
}))

const html = normalizeHtml(wrapper.html())

// Create a well-formatted expected HTML string for easy reading in the IDE
const expectedHtml = normalizeHtml(`
<table>
<tbody>
<tr>
<td>
<div style="display: contents;">First column</div>
</td>
<td>
<div style="display: contents;"><h2>Media heading</h2></div>
<div class="drupal-media" data-id="123">Media content</div>
</td>
</tr>
</tbody>
</table>
`)

// Assert the exact HTML structure
expect(html).toEqual(expectedHtml)
})

})