diff --git a/src/components/ContentNode.vue b/src/components/ContentNode.vue index 413949bcf..6b3f257ec 100644 --- a/src/components/ContentNode.vue +++ b/src/components/ContentNode.vue @@ -23,6 +23,8 @@ import Table from './ContentNode/Table.vue'; import StrikeThrough from './ContentNode/StrikeThrough.vue'; import Small from './ContentNode/Small.vue'; import BlockVideo from './ContentNode/BlockVideo.vue'; +import Column from './ContentNode/Column.vue'; +import Row from './ContentNode/Row.vue'; const BlockType = { aside: 'aside', @@ -37,6 +39,7 @@ const BlockType = { dictionaryExample: 'dictionaryExample', small: 'small', video: 'video', + row: 'row', }; const InlineType = { @@ -299,6 +302,15 @@ function renderNode(createElement, references) { }) ) : null; } + case BlockType.row: { + return createElement( + Row, { props: { columns: node.numberOfColumns } }, node.columns.map(col => ( + createElement( + Column, { props: { span: col.size } }, renderChildren(col.content), + ) + )), + ); + } case InlineType.codeVoice: return createElement(CodeVoice, {}, ( node.code diff --git a/src/components/ContentNode/Column.vue b/src/components/ContentNode/Column.vue new file mode 100644 index 000000000..82d32a44a --- /dev/null +++ b/src/components/ContentNode/Column.vue @@ -0,0 +1,41 @@ + + + + + + diff --git a/src/components/ContentNode/Row.vue b/src/components/ContentNode/Row.vue new file mode 100644 index 000000000..e400fceeb --- /dev/null +++ b/src/components/ContentNode/Row.vue @@ -0,0 +1,72 @@ + + + + + + diff --git a/tests/unit/components/ContentNode.spec.js b/tests/unit/components/ContentNode.spec.js index 744953b0c..def82b972 100644 --- a/tests/unit/components/ContentNode.spec.js +++ b/tests/unit/components/ContentNode.spec.js @@ -24,6 +24,8 @@ import LinkableHeading from 'docc-render/components/ContentNode/LinkableHeading. import StrikeThrough from 'docc-render/components/ContentNode/StrikeThrough.vue'; import Small from '@/components/ContentNode/Small.vue'; import BlockVideo from '@/components/ContentNode/BlockVideo.vue'; +import Row from '@/components/ContentNode/Row.vue'; +import Column from '@/components/ContentNode/Column.vue'; const { TableHeaderStyle } = ContentNode.constants; @@ -342,6 +344,54 @@ describe('ContentNode', () => { }); }); + describe('with type="row"', () => { + it('renders a `` and ``', () => { + const wrapper = mountWithItem({ + type: 'row', + numberOfColumns: 4, + columns: [ + { + size: 2, + content: [ + { + type: 'paragraph', + inlineContent: [ + { + type: 'text', + text: 'foo', + }, + ], + }, + ], + }, + { + content: [ + { + type: 'paragraph', + inlineContent: [ + { + type: 'text', + text: 'bar', + }, + ], + }, + ], + }, + ], + }); + const grid = wrapper.find(Row); + expect(grid.props()).toEqual({ + columns: 4, + }); + const columns = grid.findAll(Column); + expect(columns).toHaveLength(2); + expect(columns.at(0).props()).toEqual({ span: 2 }); + expect(columns.at(0).find('p').text()).toBe('foo'); + expect(columns.at(1).props()).toEqual({ span: null }); + expect(columns.at(1).find('p').text()).toBe('bar'); + }); + }); + describe('with type="codeVoice"', () => { it('renders a `CodeVoice`', () => { const wrapper = mountWithItem({ @@ -1227,7 +1277,8 @@ describe('ContentNode', () => { const content = wrapper.find(StrikeThrough); // assert the `strong` tag is rendered - expect(content.html()).toBe('2strong'); + expect(content.html()) + .toBe('2strong'); }); }); diff --git a/tests/unit/components/ContentNode/Column.spec.js b/tests/unit/components/ContentNode/Column.spec.js new file mode 100644 index 000000000..547ef8995 --- /dev/null +++ b/tests/unit/components/ContentNode/Column.spec.js @@ -0,0 +1,32 @@ +/** + * This source file is part of the Swift.org open source project + * + * Copyright (c) 2022 Apple Inc. and the Swift project authors + * Licensed under Apache License v2.0 with Runtime Library Exception + * + * See https://swift.org/LICENSE.txt for license information + * See https://swift.org/CONTRIBUTORS.txt for Swift project authors +*/ + +import Column from '@/components/ContentNode/Column.vue'; +import { shallowMount } from '@vue/test-utils'; + +const createWrapper = props => shallowMount(Column, { + slots: { + default: 'Default Content', + }, + ...props, +}); + +describe('Column', () => { + it('renders the Column', () => { + const wrapper = createWrapper(); + expect(wrapper.classes()).toContain('column'); + expect(wrapper.text()).toBe('Default Content'); + expect(wrapper.vm.style).toHaveProperty('--col-span', null); + wrapper.setProps({ + span: 5, + }); + expect(wrapper.vm.style).toHaveProperty('--col-span', 5); + }); +}); diff --git a/tests/unit/components/ContentNode/Row.spec.js b/tests/unit/components/ContentNode/Row.spec.js new file mode 100644 index 000000000..8ddcdb15f --- /dev/null +++ b/tests/unit/components/ContentNode/Row.spec.js @@ -0,0 +1,47 @@ +/** + * This source file is part of the Swift.org open source project + * + * Copyright (c) 2022 Apple Inc. and the Swift project authors + * Licensed under Apache License v2.0 with Runtime Library Exception + * + * See https://swift.org/LICENSE.txt for license information + * See https://swift.org/CONTRIBUTORS.txt for Swift project authors +*/ + +import Row from '@/components/ContentNode/Row.vue'; +import { shallowMount } from '@vue/test-utils'; + +const createWrapper = props => shallowMount(Row, { + slots: { + default: 'Slot Content', + }, + ...props, +}); + +describe('Row', () => { + it('renders the Row', () => { + const wrapper = createWrapper(); + expect(wrapper.classes()).toContain('row'); + expect(wrapper.classes()).not.toContain('with-columns'); + expect(wrapper.text()).toContain('Slot Content'); + }); + + it('renders with columns in mind', () => { + const wrapper = createWrapper({ + propsData: { + columns: 4, + }, + }); + expect(wrapper.classes()).toContain('with-columns'); + expect(wrapper.vm.style).toHaveProperty('--col-count', 4); + }); + + it('provides a --col-gap', () => { + const wrapper = createWrapper({ + propsData: { + gap: 10, + }, + }); + expect(wrapper.vm.style).toHaveProperty('--col-gap', '10px'); + }); +});