');
expect($el.data('custom')).toBe('{{templatevar}}');
});
it('("") : should accept the empty string as a name', () => {
const $el = cheerio('
');
expect($el.data('')).toBe('a');
});
it('(hyphen key) : data addribute with hyphen should be camelized ;-)', () => {
const data = $('.frey').data();
expect(data).toStrictEqual({
taste: 'sweet',
bestCollection: 'Mahony',
});
});
it('(key, value) : should set data attribute', () => {
// Adding as object.
const a = $('.frey').data({
balls: 'giandor',
});
// Adding as string.
const b = $('.linth').data('snack', 'chocoletti');
expect(() => {
a.data(4 as never, 'throw');
}).not.toThrow();
expect(a.data('balls')).toStrictEqual('giandor');
expect(b.data('snack')).toStrictEqual('chocoletti');
});
it('(key, value) : should set data for all elements in the selection', () => {
$('li').data('foo', 'bar');
expect($('li').eq(0).data('foo')).toStrictEqual('bar');
expect($('li').eq(1).data('foo')).toStrictEqual('bar');
expect($('li').eq(2).data('foo')).toStrictEqual('bar');
});
it('(map) : object map should set multiple data attributes', () => {
const { data } = $('.linth').data({
id: 'Cailler',
flop: 'Pippilotti Rist',
top: 'Frigor',
url: 'http://www.cailler.ch/',
})[0] as never;
expect(data).toHaveProperty('id', 'Cailler');
expect(data).toHaveProperty('flop', 'Pippilotti Rist');
expect(data).toHaveProperty('top', 'Frigor');
expect(data).toHaveProperty('url', 'http://www.cailler.ch/');
});
describe('(attr) : data-* attribute type coercion :', () => {
it('boolean', () => {
const $el = cheerio('
');
expect($el.data('bool')).toBe(true);
});
it('number', () => {
const $el = cheerio('
');
expect($el.data('number')).toBe(23);
});
it('number (scientific notation is not coerced)', () => {
const $el = cheerio('
');
expect($el.data('sci')).toBe('1E10');
});
it('null', () => {
const $el = cheerio('
');
expect($el.data('null')).toBe(null);
});
it('object', () => {
const $el = cheerio('
');
expect($el.data('obj')).toStrictEqual({ a: 45 });
});
it('array', () => {
const $el = cheerio('
');
expect($el.data('array')).toStrictEqual([1, 2, 3]);
});
});
it('(key, value) : should skip text nodes', () => {
const $text = load(mixedText);
const $body = $text($text('body')[0].children);
$body.data('snack', 'chocoletti');
expect($text('b').data('snack')).toBe('chocoletti');
});
});
describe('.val', () => {
let $: CheerioAPI;
beforeEach(() => {
$ = load(inputs);
});
it('(): on div should get undefined', () => {
expect($('
').val()).toBeUndefined();
});
it('(): on select should get value', () => {
const val = $('select#one').val();
expect(val).toBe('option_selected');
});
it('(): on select with no value should get text', () => {
const val = $('select#one-valueless').val();
expect(val).toBe('Option selected');
});
it('(): on select with no value should get converted HTML', () => {
const val = $('select#one-html-entity').val();
expect(val).toBe('Option
');
});
it('(): on select with no value should get text content', () => {
const val = $('select#one-nested').val();
expect(val).toBe('Option selected');
});
it('(): on option should get value', () => {
const val = $('select#one option').eq(0).val();
expect(val).toBe('option_not_selected');
});
it('(): on text input should get value', () => {
const val = $('input[type="text"]').val();
expect(val).toBe('input_text');
});
it('(): on checked checkbox should get value', () => {
const val = $('input[name="checkbox_on"]').val();
expect(val).toBe('on');
});
it('(): on unchecked checkbox should get value', () => {
const val = $('input[name="checkbox_off"]').val();
expect(val).toBe('off');
});
it('(): on valueless checkbox should get value', () => {
const val = $('input[name="checkbox_valueless"]').val();
expect(val).toBe('on');
});
it('(): on radio should get value', () => {
const val = $('input[type="radio"]').val();
expect(val).toBe('off');
});
it('(): on valueless radio should get value', () => {
const val = $('input[name="radio_valueless"]').val();
expect(val).toBe('on');
});
it('(): on multiple select should get an array of values', () => {
const val = $('select#multi').val();
expect(val).toStrictEqual(['2', '3']);
});
it('(): on multiple select with no value attribute should get an array of text content', () => {
const val = $('select#multi-valueless').val();
expect(val).toStrictEqual(['2', '3']);
});
it('(): with no selector matches should return nothing', () => {
const val = $('.nasty').val();
expect(val).toBeUndefined();
});
it('(invalid value): should only handle arrays when it has the attribute multiple', () => {
const val = $('select#one').val([]);
expect(val).not.toBeUndefined();
});
it('(value): on empty set should get `this`', () => {
const $empty = $([]);
expect($empty.val('test')).toBe($empty);
});
it('(value): on input text should set value', () => {
const element = $('input[type="text"]').val('test');
expect(element.val()).toBe('test');
});
it('(value): on select should set value', () => {
const element = $('select#one').val('option_not_selected');
expect(element.val()).toBe('option_not_selected');
});
it('(value): on option should set value', () => {
const element = $('select#one option').eq(0).val('option_changed');
expect(element.val()).toBe('option_changed');
});
it('(value): on radio should set value', () => {
const element = $('input[name="radio"]').val('off');
expect(element.val()).toBe('off');
});
it('(value): on radio with special characters should set value', () => {
const element = $('input[name="radio[brackets]"]').val('off');
expect(element.val()).toBe('off');
});
it('(values): on multiple select should set multiple values', () => {
const element = $('select#multi').val(['1', '3', '4']);
expect(element.val()).toHaveLength(3);
});
});
describe('.removeAttr', () => {
let $: CheerioAPI;
beforeEach(() => {
$ = load(fruits);
});
it('(key) : should remove a single attr', () => {
const $fruits = $('#fruits');
expect($fruits.attr('id')).not.toBeUndefined();
$fruits.removeAttr('id');
expect($fruits.attr('id')).toBeUndefined();
});
it('(key key) : should remove multiple attrs', () => {
const $apple = $('.apple');
$apple.attr('id', 'favorite');
$apple.attr('size', 'small');
expect($apple.attr('id')).toBe('favorite');
expect($apple.attr('class')).toBe('apple');
expect($apple.attr('size')).toBe('small');
$apple.removeAttr('id class');
expect($apple.attr('id')).toBeUndefined();
expect($apple.attr('class')).toBeUndefined();
expect($apple.attr('size')).toBe('small');
});
it('(key) : should return cheerio object', () => {
const obj = $('ul').removeAttr('id');
expect(obj).toBeInstanceOf($);
});
it('(key) : should skip text nodes', () => {
const $text = load(mixedText);
const $body = $text($text('body')[0].children);
$body.addClass(() => 'test');
expect($text('body').html()).toBe(
'1TEXT2',
);
$body.removeAttr('class');
expect($text('body').html()).toBe(mixedText);
});
});
describe('.hasClass', () => {
let $: CheerioAPI;
beforeEach(() => {
$ = load(fruits);
});
it('(valid class) : should return true', () => {
const cls = $('.apple').hasClass('apple');
expect(cls).toBe(true);
expect(withClass('foo').hasClass('foo')).toBe(true);
expect(withClass('foo bar').hasClass('foo')).toBe(true);
expect(withClass('bar foo').hasClass('foo')).toBe(true);
expect(withClass('bar foo bar').hasClass('foo')).toBe(true);
});
it('(invalid class) : should return false', () => {
const cls = $('#fruits').hasClass('fruits');
expect(cls).toBe(false);
expect(withClass('foo-bar').hasClass('foo')).toBe(false);
expect(withClass('foo-bar').hasClass('foo')).toBe(false);
expect(withClass('foo-bar').hasClass('foo-ba')).toBe(false);
});
it('should check multiple classes', () => {
// Add a class
$('.apple').addClass('red');
expect($('.apple').hasClass('apple')).toBe(true);
expect($('.apple').hasClass('red')).toBe(true);
// Remove one and test again
$('.apple').removeClass('apple');
expect($('li').eq(0).hasClass('apple')).toBe(false);
});
it('(empty string argument) : should return false', () => {
expect(withClass('foo').hasClass('')).toBe(false);
expect(withClass('foo bar').hasClass('')).toBe(false);
expect(withClass('foo bar').removeClass('foo').hasClass('')).toBe(false);
});
});
describe('.addClass', () => {
let $: CheerioAPI;
beforeEach(() => {
$ = load(fruits);
});
it('(first class) : should add the class to the element', () => {
const $fruits = $('#fruits');
$fruits.addClass('fruits');
const cls = $fruits.hasClass('fruits');
expect(cls).toBe(true);
});
it('(single class) : should add the class to the element', () => {
$('.apple').addClass('fruit');
const cls = $('.apple').hasClass('fruit');
expect(cls).toBe(true);
});
it('(class): adds classes to many selected items', () => {
$('li').addClass('fruit');
expect($('.apple').hasClass('fruit')).toBe(true);
expect($('.orange').hasClass('fruit')).toBe(true);
expect($('.pear').hasClass('fruit')).toBe(true);
// Mixed with text nodes
const $red = $('\n\t').addClass('red');
expect($red).toHaveLength(3);
expect($red[0].type).toBe('text');
expect($red[1].type).toBe('tag');
expect($red[2].type).toBe('text');
expect($red.hasClass('red')).toBe(true);
});
it('(class class class) : should add multiple classes to the element', () => {
$('.apple').addClass('fruit red tasty');
expect($('.apple').hasClass('apple')).toBe(true);
expect($('.apple').hasClass('fruit')).toBe(true);
expect($('.apple').hasClass('red')).toBe(true);
expect($('.apple').hasClass('tasty')).toBe(true);
});
it('(fn) : should add classes returned from the function', () => {
const $fruits = $('#fruits').children().add($('#fruits'));
const args: [i: number, className: string][] = [];
const thisVals: Element[] = [];
const toAdd = ['main', 'apple red', '', undefined];
$fruits.addClass(function (...myArgs) {
args.push(myArgs);
thisVals.push(this);
return toAdd[myArgs[0]];
});
expect(args).toStrictEqual([
[0, ''],
[1, 'apple'],
[2, 'orange'],
[3, 'pear'],
]);
expect(thisVals).toStrictEqual([
$fruits[0],
$fruits[1],
$fruits[2],
$fruits[3],
]);
expect($fruits.eq(0).hasClass('main')).toBe(true);
expect($fruits.eq(0).hasClass('apple')).toBe(false);
expect($fruits.eq(1).hasClass('apple')).toBe(true);
expect($fruits.eq(1).hasClass('red')).toBe(true);
expect($fruits.eq(2).hasClass('orange')).toBe(true);
expect($fruits.eq(3).hasClass('pear')).toBe(true);
});
});
describe('.removeClass', () => {
let $: CheerioAPI;
beforeEach(() => {
$ = load(fruits);
});
it('() : should remove all the classes', () => {
$('.pear').addClass('fruit');
$('.pear').removeClass();
expect($('.pear').attr('class')).toBeUndefined();
});
it('("") : should not modify class list', () => {
const $fruits = $('#fruits');
$fruits.children().removeClass('');
expect($('.apple')).toHaveLength(1);
});
it('(invalid class) : should not remove anything', () => {
$('.pear').removeClass('fruit');
expect($('.pear').hasClass('pear')).toBe(true);
});
it('(no class attribute) : should not throw an exception', () => {
const $vegetables = cheerio(vegetables);
expect(() => {
$('li', $vegetables).removeClass('vegetable');
}).not.toThrow();
});
it('(single class) : should remove a single class from the element', () => {
$('.pear').addClass('fruit');
expect($('.pear').hasClass('fruit')).toBe(true);
$('.pear').removeClass('fruit');
expect($('.pear').hasClass('fruit')).toBe(false);
expect($('.pear').hasClass('pear')).toBe(true);
// Remove one class from set
const $li = $('li').removeClass('orange');
expect($li.eq(0).attr('class')).toBe('apple');
expect($li.eq(1).attr('class')).toBe('');
expect($li.eq(2).attr('class')).toBe('pear');
// Mixed with text nodes
const $red = $('\n\t').removeClass(
'one',
);
expect($red).toHaveLength(3);
expect($red[0].type).toBe('text');
expect($red[1].type).toBe('tag');
expect($red[2].type).toBe('text');
expect($red.eq(1).attr('class')).toBe('');
expect($red.eq(1).prop('tagName')).toBe('UL');
});
it('(single class) : should remove a single class from multiple classes on the element', () => {
$('.pear').addClass('fruit green tasty');
expect($('.pear').hasClass('fruit')).toBe(true);
expect($('.pear').hasClass('green')).toBe(true);
expect($('.pear').hasClass('tasty')).toBe(true);
$('.pear').removeClass('green');
expect($('.pear').hasClass('fruit')).toBe(true);
expect($('.pear').hasClass('green')).toBe(false);
expect($('.pear').hasClass('tasty')).toBe(true);
});
it('(class class class) : should remove multiple classes from the element', () => {
$('.apple').addClass('fruit red tasty');
expect($('.apple').hasClass('apple')).toBe(true);
expect($('.apple').hasClass('fruit')).toBe(true);
expect($('.apple').hasClass('red')).toBe(true);
expect($('.apple').hasClass('tasty')).toBe(true);
$('.apple').removeClass('apple red tasty');
expect($('.fruit').hasClass('apple')).toBe(false);
expect($('.fruit').hasClass('red')).toBe(false);
expect($('.fruit').hasClass('tasty')).toBe(false);
expect($('.fruit').hasClass('fruit')).toBe(true);
});
it('(class) : should remove all occurrences of a class name', () => {
const $div = cheerio('');
expect($div.removeClass('x').hasClass('x')).toBe(false);
});
it('(fn) : should remove classes returned from the function', () => {
const $fruits = $('#fruits').children();
const args: [number, string][] = [];
const thisVals: Element[] = [];
const toAdd = ['apple red', '', undefined];
$fruits.removeClass(function (...myArgs) {
args.push(myArgs);
thisVals.push(this);
return toAdd[myArgs[0]];
});
expect(args).toStrictEqual([
[0, 'apple'],
[1, 'orange'],
[2, 'pear'],
]);
expect(thisVals).toStrictEqual([$fruits[0], $fruits[1], $fruits[2]]);
expect($fruits.eq(0).hasClass('apple')).toBe(false);
expect($fruits.eq(0).hasClass('red')).toBe(false);
expect($fruits.eq(1).hasClass('orange')).toBe(true);
expect($fruits.eq(2).hasClass('pear')).toBe(true);
});
it('(fn) : should no op elements without attributes', () => {
const $inputs = $(inputs);
const val = $inputs.removeClass(() => 'tasty');
expect(val).toHaveLength(15);
});
it('(fn) : should skip text nodes', () => {
const $text = load(mixedText);
const $body = $text($text('body')[0].children);
$body.addClass(() => 'test');
expect($text('body').html()).toBe(
'1TEXT2',
);
$body.removeClass(() => 'test');
expect($text('body').html()).toBe(
'1TEXT2',
);
});
});
describe('.toggleClass', () => {
let $: CheerioAPI;
beforeEach(() => {
$ = load(fruits);
});
it('(class class) : should toggle multiple classes from the element', () => {
$('.apple').addClass('fruit');
expect($('.apple').hasClass('apple')).toBe(true);
expect($('.apple').hasClass('fruit')).toBe(true);
expect($('.apple').hasClass('red')).toBe(false);
$('.apple').toggleClass('apple red');
expect($('.fruit').hasClass('apple')).toBe(false);
expect($('.fruit').hasClass('red')).toBe(true);
expect($('.fruit').hasClass('fruit')).toBe(true);
// Mixed with text nodes
const $red = $('\n\t').toggleClass(
'red',
);
expect($red).toHaveLength(3);
expect($red.hasClass('red')).toBe(true);
expect($red.hasClass('one')).toBe(true);
$red.toggleClass('one');
expect($red.hasClass('red')).toBe(true);
expect($red.hasClass('one')).toBe(false);
});
it('(class class, true) : should add multiple classes to the element', () => {
$('.apple').addClass('fruit');
expect($('.apple').hasClass('apple')).toBe(true);
expect($('.apple').hasClass('fruit')).toBe(true);
expect($('.apple').hasClass('red')).toBe(false);
$('.apple').toggleClass('apple red', true);
expect($('.fruit').hasClass('apple')).toBe(true);
expect($('.fruit').hasClass('red')).toBe(true);
expect($('.fruit').hasClass('fruit')).toBe(true);
});
it('(class true) : should add only one instance of class', () => {
$('.apple').toggleClass('tasty', true);
$('.apple').toggleClass('tasty', true);
expect($('.apple').attr('class')).toMatch(/tasty/g);
});
it('(class class, false) : should remove multiple classes from the element', () => {
$('.apple').addClass('fruit');
expect($('.apple').hasClass('apple')).toBe(true);
expect($('.apple').hasClass('fruit')).toBe(true);
expect($('.apple').hasClass('red')).toBe(false);
$('.apple').toggleClass('apple red', false);
expect($('.fruit').hasClass('apple')).toBe(false);
expect($('.fruit').hasClass('red')).toBe(false);
expect($('.fruit').hasClass('fruit')).toBe(true);
});
it('(fn) : should toggle classes returned from the function', () => {
const $ = load(food);
$('.apple').addClass('fruit');
$('.carrot').addClass('vegetable');
expect($('.apple').hasClass('fruit')).toBe(true);
expect($('.apple').hasClass('vegetable')).toBe(false);
expect($('.orange').hasClass('fruit')).toBe(false);
expect($('.orange').hasClass('vegetable')).toBe(false);
expect($('.carrot').hasClass('fruit')).toBe(false);
expect($('.carrot').hasClass('vegetable')).toBe(true);
expect($('.sweetcorn').hasClass('fruit')).toBe(false);
expect($('.sweetcorn').hasClass('vegetable')).toBe(false);
$('li').toggleClass(function () {
return $(this).parent().is('#fruits') ? 'fruit' : 'vegetable';
});
expect($('.apple').hasClass('fruit')).toBe(false);
expect($('.apple').hasClass('vegetable')).toBe(false);
expect($('.orange').hasClass('fruit')).toBe(true);
expect($('.orange').hasClass('vegetable')).toBe(false);
expect($('.carrot').hasClass('fruit')).toBe(false);
expect($('.carrot').hasClass('vegetable')).toBe(false);
expect($('.sweetcorn').hasClass('fruit')).toBe(false);
expect($('.sweetcorn').hasClass('vegetable')).toBe(true);
});
it('(fn) : should work with no initial class attribute', () => {
const $inputs = load(inputs);
$inputs('input, select').toggleClass(function () {
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion -- `get` should never return undefined here.
return $inputs(this).get(0)!.tagName === 'select'
? 'selectable'
: 'inputable';
});
expect($inputs('.selectable')).toHaveLength(6);
expect($inputs('.inputable')).toHaveLength(9);
});
it('(fn) : should skip text nodes', () => {
const $text = load(mixedText);
const $body = $text($text('body')[0].children);
$body.toggleClass(() => 'test');
expect($text('body').html()).toBe(
'1TEXT2',
);
$body.toggleClass(() => 'test');
expect($text('body').html()).toBe(
'1TEXT2',
);
});
it('(invalid) : should be a no-op for invalid inputs', () => {
const original = $('.apple');
const testAgainst = original.attr('class');
expect(original.toggleClass().attr('class')).toStrictEqual(testAgainst);
for (const value of [undefined, true, false, null, 0, 1, {}]) {
expect(
original.toggleClass(value as never).attr('class'),
).toStrictEqual(testAgainst);
}
});
});
});