Skip to content
This repository has been archived by the owner on Dec 15, 2023. It is now read-only.

feat: add description list component #1289

Merged
merged 5 commits into from
Nov 2, 2023
Merged
Show file tree
Hide file tree
Changes from 2 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
58 changes: 58 additions & 0 deletions components/description_list/description_list.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import { Canvas, Story, Subtitle, Controls, Meta } from '@storybook/blocks';
import * as DtDescriptionListStories from './description_list.stories.js';

<Meta of={DtDescriptionListStories}/>

# DtDescriptionList

<Subtitle>
Description lists are a way to group and clarify associated ideas.
They are notably useful when outlining and explaining terms, like those in a glossary.
</Subtitle>

## Base Style

<Canvas of={DtDescriptionListStories.Default} />

## Column Direction

<Canvas of={DtDescriptionListStories.ColumnDirection} />

## Long Text

<Canvas of={DtDescriptionListStories.LongText} />

## Slots, Props & Events

<Controls />

## Usage

### Import

```jsx
import { DtDescriptionList } from '@dialpad/dialtone-vue';
```

### Default

```jsx
<dt-description-list
:items="[{
term: 'Customer Intent',
description: 'Hello, I'm looking to return my TV',
}]"
/>
```

### Column Direction

```jsx
<dt-description-list
:items="[{
term: 'Customer Intent',
description: 'Hello, I'm looking to return my TV',
}]"
:direction="column"
/>
```
117 changes: 117 additions & 0 deletions components/description_list/description_list.stories.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
import { createTemplateFromVueFile } from '@/common/storybook_utils';
import DtDescriptionList from './description_list.vue';
import DtDescriptionListDefaultTemplate from './description_list_default.story.vue';

export const argTypesData = {
// Props
items: {
control: 'object',
table: {
defaultValue: {
summary: '{ term: string, description: string }[]',
},
},
},
};

export const argsData = {
direction: 'row',
gap: '400',
items: [
{
term: 'Customer Intent',
description: `Hello, I'm looking to return my TV`,
},
{
term: 'Reason',
description: 'Refound',
},
{
term: 'Country',
description: 'England',
},
{
term: 'Random',
description: 'Value',
},
],
};

const argsDataLongText = {
items: [
{
term: 'Customer Intent',
description: `Lorem ipsum dolor sit amet, consectetur adipiscing elit,
sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.`,
},
{
term: 'Three word term',
description: ` Duis aute irure dolor in reprehenderit in voluptate velit
esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat
cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.`,
},
{
term: 'Country',
description: 'England',
},
{
term: 'Random',
description: 'Value',
},
],
};

const decorator = () => ({
template: `<div
style="width: var(--dt-size-925);
overflow: hidden;
resize: horizontal;
height: auto;
border: 1px solid var(--dt-color-border-subtle);
padding: var(--dt-space-450);
borderRadius: var(--dt-size-radius-400);"
>
<story />
</div>`,
});

// Story Collection
export default {
title: 'Components/Description List',
component: DtDescriptionList,
args: argsData,
argTypes: argTypesData,
decorators: [decorator],
excludeStories: /.*Data$/,
};

// Templates
const DefaultTemplate = (args, { argTypes }) => createTemplateFromVueFile(
args,
argTypes,
DtDescriptionListDefaultTemplate,
);

// Stories
export const Default = {
render: DefaultTemplate,
args: {},
};

export const ColumnDirection = {
...Default,
args: {
direction: 'column',
},
};

export const LongText = {
...Default,
args: { ...Default.args, items: argsDataLongText.items },
};

export const WithStyles = {
...Default,
args: { ...Default.args, termClass: ['d-fw-bold', 'd-fc-disabled'] },
};
148 changes: 148 additions & 0 deletions components/description_list/description_list.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
import { mount } from '@vue/test-utils';
import DtDescriptionList from './description_list.vue';

const baseProps = {
items: [{
term: 'Customer Intent',
description: 'Hello, I\'m looking to return my TV',
}, {
term: 'Reason',
description: 'Refound',
}],
};

let mockProps = {};

describe('DtDescriptionList Tests', () => {
let wrapper;
let list;
ninamarina marked this conversation as resolved.
Show resolved Hide resolved

const updateWrapper = () => {
wrapper = mount(DtDescriptionList, {
propsData: { ...baseProps, ...mockProps },
});
};

beforeEach(() => {
updateWrapper();
});

afterEach(() => {
mockProps = {};
});

describe('Presentation Tests', () => {
it('Should render the description list', () => {
expect(wrapper).toBeDefined();
expect(wrapper.classes().includes('dt-description-list')).toBe(true);
});

describe('When direction prop is set', () => {
it('Should have correct class', () => {
mockProps = { direction: 'column' };

updateWrapper();

expect(wrapper.classes().includes('dt-description-list--column')).toBe(true);
});
});

describe('When gap prop is set', () => {
it('Should have correct class', () => {
mockProps = { gap: '300' };

updateWrapper();

expect(wrapper.classes().includes('dt-description-list--gap-300')).toBe(true);
});
});
});

describe('Accessibility Tests', () => {
beforeEach(() => {
list = wrapper.find('dl');

updateWrapper();
});

it('Should render a <dl> tag', () => {
expect(list.exists()).toBe(true);
});

it('Should contain two <dt> tags', () => {
expect(list.findAll('dt').length).toBe(2);
});

it('Should contain two <dd> tags', () => {
expect(list.findAll('dd').length).toBe(2);
});
});

describe('Validation Tests', () => {
describe('Direction Validator', () => {
describe('When provided direction is valid', () => {
it('passes custom prop validation', () => {
expect(DtDescriptionList.props.direction.validator('column')).toBe(true);
});
});

describe('When provided direction is not valid', () => {
it('fails custom prop validation', () => {
expect(DtDescriptionList.props.direction.validator('INVALID_DIRECTION')).toBe(false);
});
});
});

describe('Items Validator', () => {
describe('When provided items are valid', () => {
it('passes custom prop validation', () => {
expect(DtDescriptionList.props.items.validator(baseProps.items)).toBe(true);
});
});

describe('When provided items are not valid', () => {
it('fails custom prop validation', () => {
expect(DtDescriptionList.props.items.validator([{ invalid: 'description' }])).toBe(false);
});
});
});

describe('Gap Validator', () => {
describe('When provided gap is valid', () => {
it('passes custom prop validation', () => {
expect(DtDescriptionList.props.gap.validator('300')).toBe(true);
});
});

describe('When provided gap is not valid', () => {
it('fails custom prop validation', () => {
expect(DtDescriptionList.props.gap.validator('invalid')).toBe(false);
});
});
});
});

describe('Extendability Tests', () => {
const customClass = 'my-custom-class';

describe('When an term class is provided', () => {
it('should apply custom class to child', () => {
mockProps = { termClass: customClass };

updateWrapper();

expect(wrapper.find('dt').classes().includes(customClass)).toBe(true);
ninamarina marked this conversation as resolved.
Show resolved Hide resolved
});
});

describe('When an description class is provided', () => {
it('should apply custom class to child', () => {
mockProps = { descriptionClass: customClass };

updateWrapper();

expect(wrapper.find('dd').classes().includes(customClass)).toBe(true);
});
});
});
});
Loading
Loading