diff --git a/addon/src/properties/attribute.js b/addon/src/properties/attribute.js index be4a634e..b1b6c4b1 100644 --- a/addon/src/properties/attribute.js +++ b/addon/src/properties/attribute.js @@ -1,4 +1,3 @@ -import { $ } from '../-private/jquery'; import { findOne } from '../-private/finders'; import { getter } from '../macros/index'; @@ -63,11 +62,60 @@ import { getter } from '../macros/index'; */ export function attribute(attributeName, selector, userOptions = {}) { return getter(function (key) { - let options = { + const element = findOne(this, selector, { pageObjectKey: key, ...userOptions, - }; + }); - return $(findOne(this, selector, options)).attr(attributeName); + return attr(element, attributeName); }); } + +const BOOL_ATTRS = [ + 'checked', + 'selected', + 'async', + 'autofocus', + 'autoplay', + 'controls', + 'defer', + 'disabled', + 'hidden', + 'ismap', + 'loop', + 'multiple', + 'open', + 'readonly', + 'required', + 'scoped', +]; + +/** + * Get `Element` attribute value + * + * For backward compatibility reasons we aim to follow the way the `$.attr(` works + * @see: https://github.com/jquery/jquery/blob/a684e6ba836f7c553968d7d026ed7941e1a612d8/src/attributes/attr.js + * + * @param {Element} element + * @param {string} attributeName + * @returns string|undefined + */ +function attr(element, attributeName) { + const value = element.getAttribute(attributeName); + if (value) { + // Non-existent attributes return `null`, we normalize to undefined + return value == null ? undefined : value; + } + + const attributeNode = element.getAttributeNode(attributeName); + if (attributeNode) { + const { specified, value } = attributeNode; + + if (specified && value !== null) { + const lcAttributeName = attributeName.toLowerCase(); + return BOOL_ATTRS.includes(lcAttributeName) ? lcAttributeName : value; + } + } + + return undefined; +} diff --git a/test-app/tests/unit/-private/properties/attribute-test.ts b/test-app/tests/unit/-private/properties/attribute-test.ts index 5e6d4b5d..961e40e6 100644 --- a/test-app/tests/unit/-private/properties/attribute-test.ts +++ b/test-app/tests/unit/-private/properties/attribute-test.ts @@ -14,17 +14,19 @@ module('attribute', function (hooks) { await render(hbs``); - assert.equal(page.foo, 'a value'); + assert.strictEqual(page.foo, 'a value'); }); test("returns null when attribute doesn't exist", async function (assert) { let page = create({ - foo: attribute('placeholder', ':input'), + placeholder: attribute('placeholder', ':input'), + disabled: attribute('placeholder', ':input'), }); await render(hbs``); - assert.equal(page.foo, null); + assert.strictEqual(page.placeholder, undefined); + assert.strictEqual(page.disabled, undefined); }); test("raises an error when the element doesn't exist", async function (assert) { @@ -54,7 +56,7 @@ module('attribute', function (hooks) {
`); - assert.equal(page.foo, 'a value'); + assert.strictEqual(page.foo, 'a value'); }); test("looks for elements inside page's scope", async function (assert) { @@ -70,7 +72,7 @@ module('attribute', function (hooks) {
`); - assert.equal(page.foo, 'a value'); + assert.strictEqual(page.foo, 'a value'); }); test('resets scope', async function (assert) { @@ -85,7 +87,7 @@ module('attribute', function (hooks) {
`); - assert.equal(page.foo, 'a value'); + assert.strictEqual(page.foo, 'a value'); }); test('throws error if selector matches more than one element', async function (assert) { @@ -114,7 +116,7 @@ module('attribute', function (hooks) { `); - assert.equal(page.foo, 'a value'); + assert.strictEqual(page.foo, 'a value'); }); test('looks for elements outside the testing container', async function (assert) { @@ -129,18 +131,67 @@ module('attribute', function (hooks) { (document.getElementById('alternate-ember-testing') as HTMLElement) .innerHTML = ``; - assert.equal(page.foo, 'a value'); + assert.strictEqual(page.foo, 'a value'); + }); + + test('no value attributes', async function (assert) { + let page = create({ + scope: 'span', + disabled: attribute('disabled'), + datatest: attribute('data-test'), + }); + + await render(hbs``); + + assert.strictEqual(page.disabled, 'disabled'); + assert.strictEqual(page.datatest, ''); }); - test('normalizes value', async function (assert) { + test('uppercase', async function (assert) { let page = create({ - foo: attribute('disabled', 'span'), - nonExisting: attribute('non-existing', 'span'), + scope: 'span', + disabled: attribute('DISABLED'), + withValueSpecified: attribute('DATA-TEST') }); - await render(hbs``); + await render(hbs``); - assert.equal(page.foo, 'disabled'); - assert.strictEqual(page.nonExisting, undefined); + assert.strictEqual(page.disabled, 'disabled'); + assert.strictEqual(page.withValueSpecified, 'test'); }); + + module('tabindex', function () { + test('no value', async function (assert) { + let page = create({ + scope: 'span', + tabindex: attribute('tabindex'), + }); + + await render(hbs``); + + assert.strictEqual(page.tabindex, ''); + }); + + test('with value', async function (assert) { + let page = create({ + scope: 'span', + tabindex: attribute('tabindex'), + }); + + await render(hbs``); + + assert.strictEqual(page.tabindex, '0'); + }); + + test('uppercase', async function (assert) { + let page = create({ + scope: 'span', + tabindex: attribute('TABINDEX'), + }); + + await render(hbs``); + + assert.strictEqual(page.tabindex, ''); + }); + }) });