Skip to content

Commit a3b0557

Browse files
authored
Merge pull request #153 from posthtml/fix-overwrite
fix: do not overwrite attributes
2 parents 5d546c2 + b29ffda commit a3b0557

File tree

4 files changed

+126
-117
lines changed

4 files changed

+126
-117
lines changed

lib/index.js

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,12 @@ const plugin = (options = {}) => tree => {
1515
if (k === 'class' && node.attrs && node.attrs.class) {
1616
node.attrs.class = [...(new Set([...node.attrs.class.split(' '), ...v.split(' ')]))].join(' ')
1717
} else {
18-
const attributesToOmit = Object.keys(node.attrs || {})
19-
const { [attributesToOmit]: _, ...remainingAttributes } = options.attributes[key]
18+
const existingAttributes = node.attrs || {}
19+
const remainingAttributes = Object.fromEntries(
20+
Object.entries(
21+
options.attributes[key]).filter(([attrKey]) => !(attrKey in existingAttributes)
22+
)
23+
)
2024

2125
const attributes = options.overwrite
2226
? options.attributes[key]

test/expected/no-overwrite.html

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
11
<div id="test">test</div>
2+
<img src="avatar.jpg" alt="User name">

test/fixtures/no-overwrite.html

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
11
<div id="test">test</div>
2+
<img src="avatar.jpg" alt="User name">

test/test.js

Lines changed: 118 additions & 115 deletions
Original file line numberDiff line numberDiff line change
@@ -1,115 +1,118 @@
1-
import path from 'node:path'
2-
import { readFileSync } from 'node:fs'
3-
import {fileURLToPath} from 'node:url'
4-
import plugin from '../lib/index.js'
5-
import {test, expect} from 'vitest'
6-
import posthtml from 'posthtml'
7-
8-
const __dirname = path.dirname(fileURLToPath(import.meta.url))
9-
10-
const fixture = file => readFileSync(path.join(__dirname, 'fixtures', `${file}.html`), 'utf8').trim()
11-
const expected = file => readFileSync(path.join(__dirname, 'expected', `${file}.html`), 'utf8').trim()
12-
13-
const clean = html => html.replace(/[^\S\r\n]+$/gm, '').trim()
14-
15-
const process = (t, name, options, log = false) => {
16-
return posthtml([plugin(options)])
17-
.process(fixture(name))
18-
.then(result => log ? console.log(result.html) : clean(result.html))
19-
.then(html => expect(html).toEqual(expected(name)))
20-
}
21-
22-
test('does nothing if no attributes passed', t => {
23-
return process(t, 'nothing')
24-
})
25-
26-
test('does not overwrite existing attributes by default', t => {
27-
const attributes = {
28-
div: {
29-
id: 'new',
30-
},
31-
}
32-
return process(t, 'no-overwrite', {attributes})
33-
})
34-
35-
test('overwrites existing attributes when option is enabled', t => {
36-
const attributes = {
37-
div: {
38-
id: 'new',
39-
},
40-
}
41-
return process(t, 'overwrite', {attributes, overwrite: true})
42-
})
43-
44-
test('appends new classes', t => {
45-
const attributes = {
46-
div: {
47-
class: 'new',
48-
},
49-
}
50-
return process(t, 'append-classes', {attributes})
51-
})
52-
53-
test('tag selectors', t => {
54-
const attributes = {
55-
div: {
56-
id: 'add',
57-
},
58-
}
59-
60-
return process(t, 'tag', {attributes})
61-
})
62-
63-
test('class selectors', t => {
64-
const attributes = {
65-
'.test': {
66-
id: 'add',
67-
},
68-
}
69-
70-
return process(t, 'class', {attributes})
71-
})
72-
73-
test('id selectors', t => {
74-
const attributes = {
75-
'#test': {
76-
class: 'test',
77-
},
78-
}
79-
80-
return process(t, 'id', {attributes})
81-
})
82-
83-
test('attribute selectors', t => {
84-
const attributes = {
85-
'[role]': {
86-
'aria-roledescription': 'slide',
87-
},
88-
}
89-
90-
return process(t, 'attribute', {attributes})
91-
})
92-
93-
test('nested', t => {
94-
const attributes = {
95-
div: {
96-
class: 'parent',
97-
},
98-
span: {
99-
class: 'child',
100-
},
101-
}
102-
103-
return process(t, 'nested', {attributes})
104-
})
105-
106-
test('multiple selectors', t => {
107-
const attributes = {
108-
'div, p': {
109-
class: 'test',
110-
role: 'section',
111-
},
112-
}
113-
114-
return process(t, 'multiple', {attributes})
115-
})
1+
import path from 'node:path'
2+
import { readFileSync } from 'node:fs'
3+
import {fileURLToPath} from 'node:url'
4+
import plugin from '../lib/index.js'
5+
import {test, expect} from 'vitest'
6+
import posthtml from 'posthtml'
7+
8+
const __dirname = path.dirname(fileURLToPath(import.meta.url))
9+
10+
const fixture = file => readFileSync(path.join(__dirname, 'fixtures', `${file}.html`), 'utf8').trim()
11+
const expected = file => readFileSync(path.join(__dirname, 'expected', `${file}.html`), 'utf8').trim()
12+
13+
const clean = html => html.replace(/[^\S\r\n]+$/gm, '').trim()
14+
15+
const process = (t, name, options, log = false) => {
16+
return posthtml([plugin(options)])
17+
.process(fixture(name))
18+
.then(result => log ? console.log(result.html) : clean(result.html))
19+
.then(html => expect(html).toEqual(expected(name)))
20+
}
21+
22+
test('does nothing if no attributes passed', t => {
23+
return process(t, 'nothing')
24+
})
25+
26+
test('does not overwrite existing attributes by default', t => {
27+
const attributes = {
28+
div: {
29+
id: 'new',
30+
},
31+
img: {
32+
alt: true,
33+
},
34+
}
35+
return process(t, 'no-overwrite', {attributes})
36+
})
37+
38+
test('overwrites existing attributes when option is enabled', t => {
39+
const attributes = {
40+
div: {
41+
id: 'new',
42+
},
43+
}
44+
return process(t, 'overwrite', {attributes, overwrite: true})
45+
})
46+
47+
test('appends new classes', t => {
48+
const attributes = {
49+
div: {
50+
class: 'new',
51+
},
52+
}
53+
return process(t, 'append-classes', {attributes})
54+
})
55+
56+
test('tag selectors', t => {
57+
const attributes = {
58+
div: {
59+
id: 'add',
60+
},
61+
}
62+
63+
return process(t, 'tag', {attributes})
64+
})
65+
66+
test('class selectors', t => {
67+
const attributes = {
68+
'.test': {
69+
id: 'add',
70+
},
71+
}
72+
73+
return process(t, 'class', {attributes})
74+
})
75+
76+
test('id selectors', t => {
77+
const attributes = {
78+
'#test': {
79+
class: 'test',
80+
},
81+
}
82+
83+
return process(t, 'id', {attributes})
84+
})
85+
86+
test('attribute selectors', t => {
87+
const attributes = {
88+
'[role]': {
89+
'aria-roledescription': 'slide',
90+
},
91+
}
92+
93+
return process(t, 'attribute', {attributes})
94+
})
95+
96+
test('nested', t => {
97+
const attributes = {
98+
div: {
99+
class: 'parent',
100+
},
101+
span: {
102+
class: 'child',
103+
},
104+
}
105+
106+
return process(t, 'nested', {attributes})
107+
})
108+
109+
test('multiple selectors', t => {
110+
const attributes = {
111+
'div, p': {
112+
class: 'test',
113+
role: 'section',
114+
},
115+
}
116+
117+
return process(t, 'multiple', {attributes})
118+
})

0 commit comments

Comments
 (0)