diff --git a/lib/api/manipulation.js b/lib/api/manipulation.js
index 27d553e25e..24a6318174 100644
--- a/lib/api/manipulation.js
+++ b/lib/api/manipulation.js
@@ -364,6 +364,47 @@ exports.wrapInner = _wrap(function (el, elInsertLocation, wrapperDom) {
updateDOM(wrapperDom, el);
});
+/**
+ * The .unwrap() function, removes the parents of the set of matched elements
+ * from the DOM, leaving the matched elements in their place.
+ *
+ * @example
without selector
+ * const $ = cheerio.load(
+ * ''
+ * );
+ * $('#test p').unwrap();
+ *
+ * //=>
+ * //
Hello
+ * //
World
+ * //
+ *
+ * @example with selector
+ * const $ = cheerio.load(
+ * ''
+ * );
+ * $('#test p').unwrap('b');
+ *
+ * //=>
+ * //
Hello
+ * //
World
+ * //
+ *
+ * @param {string} [selector] - A selector to check the parent element against. If
+ * an element's parent does not match the selector, the element won't be unwrapped.
+ * @see {@link https://api.jquery.com/unwrap/}
+ * @returns {Cheerio} The instance itself, for chaining.
+ */
+exports.unwrap = function (selector) {
+ var self = this;
+ this.parent(selector)
+ .not('body')
+ .each(function (i, el) {
+ self._make(el).replaceWith(el.children);
+ });
+ return this;
+};
+
/**
* The .wrapAll() function can take any string or object that could be passed to
* the $() function to specify a DOM structure. This structure may be nested
diff --git a/test/api/manipulation.js b/test/api/manipulation.js
index b1476944db..ced60650aa 100644
--- a/test/api/manipulation.js
+++ b/test/api/manipulation.js
@@ -324,6 +324,89 @@ describe('$(...)', function () {
});
});
+ describe('.unwrap', function () {
+ var $elem;
+ var unwrapspans = [
+ '',
+ '
ab
',
+ '
cd
',
+ '
ef
',
+ '
',
+ ].join('');
+
+ beforeEach(function () {
+ $elem = cheerio.load(unwrapspans);
+ });
+
+ it('() : should be unwrap span elements', function () {
+ var abcd = $elem('#unwrap1 > span, #unwrap2 > span').get();
+ var abcdef = $elem('#unwrap span').get();
+
+ // make #unwrap1 and #unwrap2 go away
+ expect(
+ $elem('#unwrap1 span').add('#unwrap2 span:first-child').unwrap()
+ ).toHaveLength(3);
+
+ //.toEqual
+ // all four spans should still exist
+ expect($elem('#unwrap > span').get()).toEqual(abcd);
+
+ // make all b elements in #unwrap3 go away
+ expect($elem('#unwrap3 span').unwrap().get()).toEqual(
+ $elem('#unwrap3 > span').get()
+ );
+
+ // make #unwrap3 go away
+ expect($elem('#unwrap3 span').unwrap().get()).toEqual(
+ $elem('#unwrap > span.unwrap3').get()
+ );
+
+ // #unwrap only contains 6 child spans
+ expect($elem('#unwrap').children().get()).toEqual(abcdef);
+
+ // make the 6 spans become children of body
+ expect($elem('#unwrap > span').unwrap().get()).toEqual(
+ $elem('body > span.unwrap').get()
+ );
+
+ // can't unwrap children of body
+ expect($elem('body > span.unwrap').unwrap().get()).toEqual(
+ $elem('body > span.unwrap').get()
+ );
+
+ // can't unwrap children of body
+ expect($elem('body > span.unwrap').unwrap().get()).toEqual(abcdef);
+
+ // can't unwrap children of body
+ expect($elem('body > span.unwrap').get()).toEqual(abcdef);
+ });
+
+ it('(selector) : should only unwrap element parent what specified', function () {
+ var abcd = $elem('#unwrap1 > span, #unwrap2 > span').get();
+ // var abcdef = $elem('#unwrap span').get();
+
+ // Shouldn't unwrap, no match
+ $elem('#unwrap1 span').unwrap('#unwrap2');
+ expect($elem('#unwrap1')).toHaveLength(1);
+
+ // Shouldn't unwrap, no match
+ $elem('#unwrap1 span').unwrap('span');
+ expect($elem('#unwrap1')).toHaveLength(1);
+
+ // Unwraps
+ $elem('#unwrap1 span').unwrap('#unwrap1');
+ expect($elem('#unwrap1')).toHaveLength(0);
+
+ // Should not unwrap - unmatched unwrap
+ $elem('#unwrap2 span').unwrap('quote');
+ expect($elem('#unwrap > span')).toHaveLength(2);
+
+ // Check return values - matched unwrap
+ $elem('#unwrap2 span').unwrap('#unwrap2');
+ expect($elem('#unwrap > span').get()).toEqual(abcd);
+ });
+ });
+
describe('.wrapAll', function () {
var doc;
var $inner;