Skip to content

Commit 248e136

Browse files
Merge pull request #5 from thomasthiebaud/refactor-options
Refactor options
2 parents 412f235 + 3f5c3e2 commit 248e136

File tree

7 files changed

+88
-52
lines changed

7 files changed

+88
-52
lines changed

README.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,11 @@ By default, we are sanitizing the html input using `DOMPurify` module. You can o
6363

6464
**IMPORTANT** You cannot override `RETURN_DOM`, `RETURN_DOM_FRAGMENT` and `RETURN_DOM_IMPORT` because they are used internaly by the library.
6565

66+
### Other options
67+
68+
**useFragment** (default `false`): Return a Fragment instead of an array.
69+
**useAsKey** (default `[key]`): Ordered list of attributes to use as a key. Use the first one that matches or `null`
70+
6671
## How to contribute ?
6772

6873
This repo enforce commit style so the release process is automatic. Commits must look like:

package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,8 @@
99
"commitmsg": "commitlint -E GIT_PARAMS",
1010
"lint": "tslint --project .",
1111
"lint:fix": "tslint --fix --project .",
12-
"test": "jest --config ./config/jest.config.js"
12+
"test": "jest --config ./config/jest.config.js",
13+
"test:watch": "jest --config ./config/jest.config.js --watch"
1314
},
1415
"repository": {
1516
"type": "git",

src/config.ts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ export interface DOMConfig {
4545
FORBID_ATTR?: string[]
4646
FORBID_TAGS?: string[]
4747
FORCE_BODY?: boolean
48+
IN_PLACE?: boolean
4849
KEEP_CONTENT?: boolean
4950
RETURN_DOM?: boolean
5051
RETURN_DOM_FRAGMENT?: boolean
@@ -64,7 +65,8 @@ export interface DOMConfig {
6465
export interface Config {
6566
dom?: DOMConfig
6667
overrides?: SelectorsToElement,
67-
generatesKeys?: boolean,
68+
useFragment?: boolean,
69+
useAsKey: string[],
6870
}
6971

7072
/**
@@ -76,6 +78,7 @@ const mandatoryConfig: Config = {
7678
RETURN_DOM_FRAGMENT: true,
7779
RETURN_DOM_IMPORT: false,
7880
},
81+
useAsKey: ['key'],
7982
}
8083

8184
/**
@@ -85,7 +88,8 @@ const defaultConfig: Config = {
8588
dom: {
8689
ADD_ATTR: ['key'],
8790
},
88-
generatesKeys: false,
91+
useAsKey: ['key'],
92+
useFragment: false,
8993
}
9094

9195
export function getConfig(config?: Partial<Config>): Config {

src/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import * as dom from './dom'
33
import { override } from './override'
44
import { render } from './react'
55

6-
export function parse(html: string, userOptions?: Config): React.ReactNode[] {
6+
export function parse(html: string, userOptions?: Config): React.ReactNode[] | React.ReactFragment {
77
if (typeof html !== 'string') {
88
throw new TypeError('First argument must be a string.')
99
}

src/react.ts

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -63,10 +63,16 @@ function transformAttributes(attributesMap: NamedNodeMap, options: Config): Attr
6363
} else {
6464
transformedAttributes[key] = attributes[key]
6565
}
66+
67+
if (!transformedAttributes.key) {
68+
const isKey = options.useAsKey.some((possibleKey) => possibleKey === key)
69+
70+
if (isKey) {
71+
transformedAttributes.key = attributes[key]
72+
}
73+
}
6674
})
67-
if (options.generatesKeys) {
68-
transformedAttributes.key = attributes.key || attributes.id || Math.random().toString(36).substr(2, 5)
69-
}
75+
7076
return transformedAttributes
7177
}
7278

@@ -98,8 +104,8 @@ function transform(element: EnrichedElement, options: Config) {
98104
}
99105
}
100106

101-
export function render(nodes: NodeListOf<Node & ChildNode>, options: Config): React.ReactNode[] {
102-
const elements = []
107+
export function render(nodes: NodeListOf<Node & ChildNode>, options: Config): React.ReactNode[] | React.ReactFragment {
108+
const elements: React.ReactNode[] = []
103109

104110
for (let i = 0; i < nodes.length; i++) {
105111
const node = nodes.item(i)
@@ -111,5 +117,8 @@ export function render(nodes: NodeListOf<Node & ChildNode>, options: Config): Re
111117
}
112118
}
113119

120+
if (options.useFragment) {
121+
return React.createElement(React.Fragment, null, ...elements)
122+
}
114123
return elements
115124
}

tests/config.spec.ts

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,8 @@ describe('Options', () => {
1111
RETURN_DOM_FRAGMENT: true,
1212
RETURN_DOM_IMPORT: false,
1313
},
14-
generatesKeys: false,
14+
useAsKey: ['key'],
15+
useFragment: false,
1516
})
1617
})
1718

@@ -29,7 +30,8 @@ describe('Options', () => {
2930
RETURN_DOM_FRAGMENT: true,
3031
RETURN_DOM_IMPORT: false,
3132
},
32-
generatesKeys: false,
33+
useAsKey: ['key'],
34+
useFragment: false,
3335
})
3436
})
3537

@@ -46,7 +48,8 @@ describe('Options', () => {
4648
RETURN_DOM_FRAGMENT: true,
4749
RETURN_DOM_IMPORT: false,
4850
},
49-
generatesKeys: false,
51+
useAsKey: ['key'],
52+
useFragment: false,
5053
})
5154
})
5255
})

tests/react.spec.ts

Lines changed: 54 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -3,54 +3,20 @@ import * as React from 'react'
33

44
import { render } from '../src/react'
55

6-
function shallowWrapper(elements: React.ReactNode[]) {
6+
function shallowWrapper(elements: React.ReactNode[] | React.ReactFragment) {
77
return shallow(React.createElement('div', null, elements))
88
}
99

1010
describe('React', () => {
1111
describe('#render', () => {
12-
describe('generatesKeys', () => {
13-
it('should use key if provided', () => {
14-
const div = document.createElement('div')
15-
const link = document.createElement('a')
16-
link.textContent = 'Link'
17-
link.setAttribute('key', 'link')
18-
div.appendChild(link)
19-
20-
const wrapper = shallowWrapper(render(div.childNodes, { generatesKeys: true }))
21-
expect(wrapper.find('a').key()).toEqual('link')
22-
})
23-
24-
it('should use id if provided and key is not here', () => {
25-
const div = document.createElement('div')
26-
const link = document.createElement('a')
27-
link.textContent = 'Link'
28-
link.setAttribute('id', 'link')
29-
div.appendChild(link)
30-
31-
const wrapper = shallowWrapper(render(div.childNodes, { generatesKeys: true }))
32-
expect(wrapper.find('a').key()).toEqual('link')
33-
})
34-
35-
it('should use a random key if key nor id are specified', () => {
36-
const div = document.createElement('div')
37-
const link = document.createElement('a')
38-
link.textContent = 'Link'
39-
div.appendChild(link)
40-
41-
const wrapper = shallowWrapper(render(div.childNodes, { generatesKeys: true }))
42-
expect(wrapper.find('a').key()).toHaveLength(5)
43-
})
44-
})
45-
4612
it('should render a element node', () => {
4713
const div = document.createElement('div')
4814
const link = document.createElement('a')
4915
link.textContent = 'Link'
5016
link.setAttribute('key', 'link')
5117
div.appendChild(link)
5218

53-
const wrapper = shallowWrapper(render(div.childNodes, {}))
19+
const wrapper = shallowWrapper(render(div.childNodes, { useAsKey: ['key'] }))
5420
expect(wrapper.find('a').text()).toEqual('Link')
5521
})
5622

@@ -60,7 +26,7 @@ describe('React', () => {
6026
newLine.setAttribute('key', 'test')
6127
div.appendChild(newLine)
6228

63-
const wrapper = shallowWrapper(render(div.childNodes, {}))
29+
const wrapper = shallowWrapper(render(div.childNodes, { useAsKey: ['key'] }))
6430
expect(wrapper.find('br')).toHaveLength(1)
6531
})
6632

@@ -69,7 +35,7 @@ describe('React', () => {
6935
const text = document.createTextNode('Text')
7036
div.appendChild(text)
7137

72-
const wrapper = shallowWrapper(render(div.childNodes, {}))
38+
const wrapper = shallowWrapper(render(div.childNodes, { useAsKey: ['key'] }))
7339
expect(wrapper.text()).toEqual('Text')
7440
})
7541

@@ -78,7 +44,7 @@ describe('React', () => {
7844
const comment = document.createComment('Comment')
7945
div.appendChild(comment)
8046

81-
const wrapper = shallowWrapper(render(div.childNodes, {}))
47+
const wrapper = shallowWrapper(render(div.childNodes, { useAsKey: ['key'] }))
8248
expect(wrapper.children()).toHaveLength(0)
8349
})
8450

@@ -90,11 +56,59 @@ describe('React', () => {
9056
span.setAttribute('key', 'test')
9157
div.appendChild(span)
9258

93-
const wrapper = shallowWrapper(render(div.childNodes, {}))
59+
const wrapper = shallowWrapper(render(div.childNodes, { useAsKey: ['key'] }))
9460
expect(wrapper.find('span')).toHaveLength(1)
9561
expect(wrapper.find('span').prop('htmlFor')).toEqual('test')
9662
expect(wrapper.find('span').prop('className')).toEqual('test')
9763
expect(wrapper.find('span').key()).toEqual('test')
9864
})
9965
})
66+
67+
describe('Options', () => {
68+
describe('#useFragment', () => {
69+
it('should return a Fragment', () => {
70+
const div = document.createElement('div')
71+
const link = document.createElement('a')
72+
link.textContent = 'Link'
73+
div.appendChild(link)
74+
75+
const wrapper = shallowWrapper(render(div.childNodes, { useFragment: true, useAsKey: ['key'] }))
76+
expect(wrapper.find('a').text()).toEqual('Link')
77+
})
78+
})
79+
80+
describe('#useAsKey', () => {
81+
it('should use key as a default key', () => {
82+
const div = document.createElement('div')
83+
const link = document.createElement('a')
84+
link.textContent = 'Link'
85+
link.setAttribute('key', 'link')
86+
div.appendChild(link)
87+
88+
const wrapper = shallowWrapper(render(div.childNodes, { useFragment: true, useAsKey: ['key'] }))
89+
expect(wrapper.find('a').key()).toEqual('link')
90+
})
91+
92+
it('should use other element in the list as fallback key', () => {
93+
const div = document.createElement('div')
94+
const link = document.createElement('a')
95+
link.textContent = 'Link'
96+
link.setAttribute('class', 'fallback key')
97+
div.appendChild(link)
98+
99+
const wrapper = shallowWrapper(render(div.childNodes, { useFragment: true, useAsKey: ['key', 'id', 'class'] }))
100+
expect(wrapper.find('a').key()).toEqual('fallback key')
101+
})
102+
103+
it('should use null if no key match', () => {
104+
const div = document.createElement('div')
105+
const link = document.createElement('a')
106+
link.textContent = 'Link'
107+
div.appendChild(link)
108+
109+
const wrapper = shallowWrapper(render(div.childNodes, { useFragment: true, useAsKey: ['key', 'id'] }))
110+
expect(wrapper.find('a').key()).toEqual(null)
111+
})
112+
})
113+
})
100114
})

0 commit comments

Comments
 (0)