From b6ce4306f9560e346d29253dd87fd085c860340b Mon Sep 17 00:00:00 2001 From: mlasky Date: Fri, 14 Jun 2019 01:47:57 -0400 Subject: [PATCH 01/32] Added npm package lodash.merge Added lodash.merge to handle deep merging in role-helpers. --- package.json | 1 + 1 file changed, 1 insertion(+) diff --git a/package.json b/package.json index a4d0c662..dda79a63 100644 --- a/package.json +++ b/package.json @@ -45,6 +45,7 @@ "@babel/runtime": "^7.4.5", "@sheerun/mutationobserver-shim": "^0.3.2", "aria-query": "3.0.0", + "lodash.merge": "^4.6.1", "pretty-format": "^24.8.0", "wait-for-expect": "^1.2.0" }, From c5abc464fd92909ad47ea56876aecdf7329846a0 Mon Sep 17 00:00:00 2001 From: mlasky Date: Fri, 14 Jun 2019 01:50:08 -0400 Subject: [PATCH 02/32] Add `getRoles` and `logRoles` utilities #282 Implements requirements of https://github.com/testing-library/dom-testing-library/issues/282 Added getRoles and logRoles to a new file, role-helpers.js. Moved buildElementRoleList from src/queries/role.js to src/role-helpers.js This function was also needed in role-helpers, and it didn't seem proper to to expert it from queries/role with a bunch of queries. Makes more sense to be in the helper file i think. --- src/__tests__/role-helpers.js | 107 ++++++++++++++++++++++++++++++++++ src/queries/role.js | 38 +----------- src/role-helpers.js | 85 +++++++++++++++++++++++++++ 3 files changed, 193 insertions(+), 37 deletions(-) create mode 100644 src/__tests__/role-helpers.js create mode 100644 src/role-helpers.js diff --git a/src/__tests__/role-helpers.js b/src/__tests__/role-helpers.js new file mode 100644 index 00000000..b7e384fa --- /dev/null +++ b/src/__tests__/role-helpers.js @@ -0,0 +1,107 @@ +import {getRoles, logRoles} from '../role-helpers' + +function setup() { + const section = document.createElement('section') + const h1 = document.createElement('h1') + const h2 = document.createElement('h2') + const h3 = document.createElement('h3') + const nav = document.createElement('nav') + const article = document.createElement('article') + const ul = document.createElement('ul') + const li1 = document.createElement('li') + const li2 = document.createElement('li') + const table = document.createElement('table') + const tr = document.createElement('tr') + const td1 = document.createElement('td') + const td2 = document.createElement('td') + const td3 = document.createElement('td') + const formEl = document.createElement('form') + const input = document.createElement('input') + const input2 = document.createElement('input') + + section.appendChild(h1) + section.appendChild(nav) + section.appendChild(h2) + section.appendChild(h3) + section.appendChild(article) + article.appendChild(ul) + ul.appendChild(li1) + ul.appendChild(li2) + article.appendChild(table) + table.appendChild(tr) + tr.appendChild(td1) + tr.appendChild(td2) + tr.appendChild(td3) + article.appendChild(formEl) + formEl.appendChild(input) + formEl.appendChild(input2) + + return { + section, + h1, + h2, + h3, + nav, + article, + ul, + li1, + li2, + table, + tr, + td1, + td2, + td3, + formEl, + input, + input2, + } +} +test('getRoles is a function', () => { + expect(typeof getRoles).toBe('function') +}) + +test('getRoles returns expected roles for various dom nodes', () => { + const { + section, + h1, + h2, + h3, + nav, + article, + ul, + li1, + li2, + table, + tr, + td1, + td2, + td3, + formEl, + input, + input2, + } = setup() + + expect(getRoles(section)).toEqual({ + region: [section], + heading: [h1, h2, h3], + navigation: [nav], + article: [article], + list: [ul], + listitem: [li1, li2], + table: [table], + row: [tr], + cell: [td1, td2, td3], + form: [formEl], + textbox: [input, input2], + }) +}) + +test('logRoles is a function', () => { + expect(typeof logRoles).toBe('function') +}) + +test('logRoles logs expected roles for various dom nodes', () => { + const {section} = setup() + + expect(logRoles(section)).toMatchSnapshot() +}) diff --git a/src/queries/role.js b/src/queries/role.js index 62f0dde6..e5a7fd5a 100644 --- a/src/queries/role.js +++ b/src/queries/role.js @@ -1,41 +1,5 @@ +import {elementRoleList} from '../role-helpers' import {buildQueries, fuzzyMatches, makeNormalizer, matches} from './all-utils' -import {elementRoles} from 'aria-query' - -function buildElementRoleList(elementRolesMap) { - function makeElementSelector({name, attributes = []}) { - return `${name}${attributes - .map(({name: attributeName, value}) => `[${attributeName}=${value}]`) - .join('')}` - } - - function getSelectorSpecificity({attributes = []}) { - return attributes.length - } - - function bySelectorSpecificity( - {specificity: leftSpecificity}, - {specificity: rightSpecificity}, - ) { - return rightSpecificity - leftSpecificity - } - - let result = [] - - for (const [element, roles] of elementRolesMap.entries()) { - result = [ - ...result, - { - selector: makeElementSelector(element), - roles: Array.from(roles), - specificity: getSelectorSpecificity(element), - }, - ] - } - - return result.sort(bySelectorSpecificity) -} - -const elementRoleList = buildElementRoleList(elementRoles) function queryAllByRole( container, diff --git a/src/role-helpers.js b/src/role-helpers.js new file mode 100644 index 00000000..c48f7048 --- /dev/null +++ b/src/role-helpers.js @@ -0,0 +1,85 @@ +import {elementRoles} from 'aria-query' +import merge from 'lodash.merge' +import {debugDOM} from './query-helpers' + +function buildElementRoleList(elementRolesMap) { + function makeElementSelector({name, attributes = []}) { + return `${name}${attributes + .map(({name: attributeName, value}) => `[${attributeName}=${value}]`) + .join('')}` + } + + function getSelectorSpecificity({attributes = []}) { + return attributes.length + } + + function bySelectorSpecificity( + {specificity: leftSpecificity}, + {specificity: rightSpecificity}, + ) { + return rightSpecificity - leftSpecificity + } + + let result = [] + + for (const [element, roles] of elementRolesMap.entries()) { + result = [ + ...result, + { + selector: makeElementSelector(element), + roles: Array.from(roles), + specificity: getSelectorSpecificity(element), + }, + ] + } + + return result.sort(bySelectorSpecificity) +} + +const elementRoleList = buildElementRoleList(elementRoles) +const elementRoleMap = elementRoleList.reduce( + (acc, {selector, roles}) => ({...acc, [selector]: roles}), + {}, +) + +function getRoles(container) { + function getRolesForElements(elements) { + return elements + .filter(el => el.tagName !== undefined) + .map(el => [el, elementRoleMap[el.tagName.toLowerCase()]]) + .reduce( + (acc, [el, roleName]) => + acc[roleName] + ? {...acc, [roleName]: [...acc[roleName], el]} + : {...acc, [roleName]: [el]}, + {}, + ) + } + + const elements = Array.isArray(container) ? container : [container] + const childRoles = elements.reduce( + (acc, el) => ({...acc, ...getRoles(Array.from(el.childNodes))}), + {}, + ) + + return merge(getRolesForElements(elements), childRoles) +} + +function logRoles(container) { + const roles = getRoles(container) + + return Object.entries(roles) + .map( + ([role, elements]) => + `${role}:\n${elements.map(el => debugDOM(el)).join('\n')}\n\n`, + ) + .join('') +} + +export { + getRoles, + logRoles, + buildElementRoleList, + elementRoleList, + elementRoleMap, +} From eea0dfe051f4cc7518b2157c493c30549b5781ac Mon Sep 17 00:00:00 2001 From: mlasky Date: Fri, 14 Jun 2019 02:54:59 -0400 Subject: [PATCH 03/32] Updated logRole test to not rely on snapshot Thinking it over, using the snapshot didn't make much sense either. So Now this test just checks loosely that the output has the expected labels followed by expected element names --- src/__tests__/role-helpers.js | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/src/__tests__/role-helpers.js b/src/__tests__/role-helpers.js index b7e384fa..7b5a96c6 100644 --- a/src/__tests__/role-helpers.js +++ b/src/__tests__/role-helpers.js @@ -102,6 +102,19 @@ test('logRoles is a function', () => { test('logRoles logs expected roles for various dom nodes', () => { const {section} = setup() + const output = logRoles(section) - expect(logRoles(section)).toMatchSnapshot() + expect(/region:[^:]+section/i.test(output)).toBe(true) + expect(/heading:[^:]+h1/i.test(output)).toBe(true) + expect(/heading:[^:]+h2/i.test(output)).toBe(true) + expect(/heading:[^:]+h3/i.test(output)).toBe(true) + expect(/navigation:[^:]+nav/i.test(output)).toBe(true) + expect(/article:[^:]+article/i.test(output)).toBe(true) + expect(/list:[^:]+ul/i.test(output)).toBe(true) + expect(/listitem:[^:]+li[^:]+li/i.test(output)).toBe(true) + expect(/table:[^:]+table/i.test(output)).toBe(true) + expect(/row:[^:]+tr/i.test(output)).toBe(true) + expect(/cell:[^:]+td[^:]+td[^:]+td/i.test(output)).toBe(true) + expect(/form:[^:]+form/i.test(output)).toBe(true) + expect(/textbox:[^:]+input[^:]+input/i.test(output)).toBe(true) }) From 608681d702ea830fc40c0499713860c7eff39efe Mon Sep 17 00:00:00 2001 From: mlasky Date: Fri, 14 Jun 2019 04:35:00 -0400 Subject: [PATCH 04/32] Added role-helpers export to index.js --- src/index.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/index.js b/src/index.js index eede8d4a..33a10a41 100644 --- a/src/index.js +++ b/src/index.js @@ -12,6 +12,7 @@ export * from './get-node-text' export * from './events' export * from './get-queries-for-element' export * from './query-helpers' +export * from './role-helpers' export * from './pretty-dom' export {configure} from './config' From 8c4ca7ca19eddedab1bb6822efded2720e707c2e Mon Sep 17 00:00:00 2001 From: mlasky Date: Fri, 14 Jun 2019 21:15:01 -0400 Subject: [PATCH 05/32] Changed formatting of logRoles No longer shows child nodes in output cleaerer distinction between groups --- src/__tests__/role-helpers.js | 25 ++++++++++++------------- src/role-helpers.js | 13 +++++++++---- 2 files changed, 21 insertions(+), 17 deletions(-) diff --git a/src/__tests__/role-helpers.js b/src/__tests__/role-helpers.js index 7b5a96c6..ca52f249 100644 --- a/src/__tests__/role-helpers.js +++ b/src/__tests__/role-helpers.js @@ -1,3 +1,4 @@ +/* eslint-disable no-control-regex */ import {getRoles, logRoles} from '../role-helpers' function setup() { @@ -104,17 +105,15 @@ test('logRoles logs expected roles for various dom nodes', () => { const {section} = setup() const output = logRoles(section) - expect(/region:[^:]+section/i.test(output)).toBe(true) - expect(/heading:[^:]+h1/i.test(output)).toBe(true) - expect(/heading:[^:]+h2/i.test(output)).toBe(true) - expect(/heading:[^:]+h3/i.test(output)).toBe(true) - expect(/navigation:[^:]+nav/i.test(output)).toBe(true) - expect(/article:[^:]+article/i.test(output)).toBe(true) - expect(/list:[^:]+ul/i.test(output)).toBe(true) - expect(/listitem:[^:]+li[^:]+li/i.test(output)).toBe(true) - expect(/table:[^:]+table/i.test(output)).toBe(true) - expect(/row:[^:]+tr/i.test(output)).toBe(true) - expect(/cell:[^:]+td[^:]+td[^:]+td/i.test(output)).toBe(true) - expect(/form:[^:]+form/i.test(output)).toBe(true) - expect(/textbox:[^:]+input[^:]+input/i.test(output)).toBe(true) + expect(/region[^<]+
[^<]+

[^<]+

- `${role}:\n${elements.map(el => debugDOM(el)).join('\n')}\n\n`, - ) + .map(([role, elements]) => { + const numDelimeters = 42 - role.length - 1 + const delimeterBar = [...Array(numDelimeters)].map(_ => '#').join('') + const elementsString = elements + .map(el => `${debugDOM(el.cloneNode(false))}`) + .join('\n\n\t') + + return `${role} ${delimeterBar}\n\n\t${elementsString}\n\n\n` + }) .join('') } From cf3c1c56d3168cca7230b9e300af92f8f3ec4039 Mon Sep 17 00:00:00 2001 From: mlasky Date: Fri, 14 Jun 2019 21:16:33 -0400 Subject: [PATCH 06/32] Removed some tests that weren't needed --- src/__tests__/role-helpers.js | 7 ------- 1 file changed, 7 deletions(-) diff --git a/src/__tests__/role-helpers.js b/src/__tests__/role-helpers.js index ca52f249..d5a32f56 100644 --- a/src/__tests__/role-helpers.js +++ b/src/__tests__/role-helpers.js @@ -57,9 +57,6 @@ function setup() { input2, } } -test('getRoles is a function', () => { - expect(typeof getRoles).toBe('function') -}) test('getRoles returns expected roles for various dom nodes', () => { const { @@ -97,10 +94,6 @@ test('getRoles returns expected roles for various dom nodes', () => { }) }) -test('logRoles is a function', () => { - expect(typeof logRoles).toBe('function') -}) - test('logRoles logs expected roles for various dom nodes', () => { const {section} = setup() const output = logRoles(section) From b179086583a87f307059aca8083c7efa677d8f5d Mon Sep 17 00:00:00 2001 From: mlasky Date: Fri, 14 Jun 2019 23:46:26 -0400 Subject: [PATCH 07/32] Refactored getRoles and updated related tests Fixed bug in getRoles where deeply nested children could overwrtie each other getRoles is now simlper The tests for getRoles and logRoles now use a more realistic DOM tree --- src/__tests__/role-helpers.js | 132 ++++++++++++++++++++-------------- src/role-helpers.js | 39 +++++----- 2 files changed, 97 insertions(+), 74 deletions(-) diff --git a/src/__tests__/role-helpers.js b/src/__tests__/role-helpers.js index d5a32f56..4fb71856 100644 --- a/src/__tests__/role-helpers.js +++ b/src/__tests__/role-helpers.js @@ -1,41 +1,67 @@ /* eslint-disable no-control-regex */ import {getRoles, logRoles} from '../role-helpers' +import {render} from './helpers/test-utils' function setup() { - const section = document.createElement('section') - const h1 = document.createElement('h1') - const h2 = document.createElement('h2') - const h3 = document.createElement('h3') - const nav = document.createElement('nav') - const article = document.createElement('article') - const ul = document.createElement('ul') - const li1 = document.createElement('li') - const li2 = document.createElement('li') - const table = document.createElement('table') - const tr = document.createElement('tr') - const td1 = document.createElement('td') - const td2 = document.createElement('td') - const td3 = document.createElement('td') - const formEl = document.createElement('form') - const input = document.createElement('input') - const input2 = document.createElement('input') + const {getByTestId} = render(` +
+
+ `) + + const section = getByTestId('main-content') + const h1 = getByTestId('main-heading') + const h2 = getByTestId('sub-heading') + const h3 = getByTestId('tertiary-heading') + const nav = getByTestId('nav-bar') + const article = getByTestId('featured-article') + const aUl = getByTestId('a-list') + const aLi1 = getByTestId('a-list-item-1') + const aLi2 = getByTestId('a-list-item-2') + const bUl = getByTestId('b-list') + const bLi1 = getByTestId('b-list-item-1') + const bLi2 = getByTestId('b-list-item-2') + const table = getByTestId('a-table') + const tbody = getByTestId('a-tbody') + const tr = getByTestId('a-row') + const td1 = getByTestId('a-cell-1') + const td2 = getByTestId('a-cell-2') + const td3 = getByTestId('a-cell-3') + const formEl = getByTestId('a-form') + const input = getByTestId('a-input-1') + const input2 = getByTestId('a-input-2') return { section, @@ -44,10 +70,14 @@ function setup() { h3, nav, article, - ul, - li1, - li2, + aUl, + aLi1, + aLi2, + bUl, + bLi1, + bLi2, table, + tbody, tr, td1, td2, @@ -66,10 +96,14 @@ test('getRoles returns expected roles for various dom nodes', () => { h3, nav, article, - ul, - li1, - li2, + aUl, + aLi1, + aLi2, + bUl, + bLi1, + bLi2, table, + tbody, tr, td1, td2, @@ -84,13 +118,14 @@ test('getRoles returns expected roles for various dom nodes', () => { heading: [h1, h2, h3], navigation: [nav], article: [article], - list: [ul], - listitem: [li1, li2], + list: [aUl, bUl], + listitem: [aLi1, aLi2, bLi1, bLi2], table: [table], row: [tr], cell: [td1, td2, td3], form: [formEl], textbox: [input, input2], + rowgroup: [tbody], }) }) @@ -98,15 +133,8 @@ test('logRoles logs expected roles for various dom nodes', () => { const {section} = setup() const output = logRoles(section) - expect(/region[^<]+
[^<]+

[^<]+

el.tagName !== undefined) - .map(el => [el, elementRoleMap[el.tagName.toLowerCase()]]) - .reduce( - (acc, [el, roleName]) => - acc[roleName] - ? {...acc, [roleName]: [...acc[roleName], el]} - : {...acc, [roleName]: [el]}, - {}, - ) + function flattenDOM(node) { + return [ + node, + ...Array.from(node.children).reduce( + (acc, c) => (c.tagName ? [...acc, ...flattenDOM(c)] : acc), + [], + ), + ] } - const elements = Array.isArray(container) ? container : [container] - const childRoles = elements.reduce( - (acc, el) => ({...acc, ...getRoles(Array.from(el.childNodes))}), - {}, - ) - - return merge(getRolesForElements(elements), childRoles) + return flattenDOM(container).reduce((acc, node) => { + const role = elementRoleMap[node.tagName.toLowerCase()] + return Array.isArray(acc[role]) + ? {...acc, [role]: [...acc[role], node]} + : {...acc, [role]: [node]} + }, []) } function logRoles(container) { @@ -70,13 +65,13 @@ function logRoles(container) { return Object.entries(roles) .map(([role, elements]) => { - const numDelimeters = 42 - role.length - 1 + const numDelimeters = 42 - role.length - 1 // 42 is arbitrary const delimeterBar = [...Array(numDelimeters)].map(_ => '#').join('') const elementsString = elements .map(el => `${debugDOM(el.cloneNode(false))}`) - .join('\n\n\t') + .join('\n\n') - return `${role} ${delimeterBar}\n\n\t${elementsString}\n\n\n` + return `${role} ${delimeterBar}\n\n${elementsString}\n\n\n` }) .join('') } From 556857f704e0f695dd2ab8314c395bc5aa84a588 Mon Sep 17 00:00:00 2001 From: mlasky Date: Fri, 14 Jun 2019 23:56:47 -0400 Subject: [PATCH 08/32] Removed export of buildElementRoleList buildElementRoleList doesn't need to be exported at this time. Previously, src/queries/roles used that function to create elementRoleList variable, but since role-helpers needed that as well it's just created in there and only elementRoleList is exported --- src/role-helpers.js | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/src/role-helpers.js b/src/role-helpers.js index 44cd7a0a..9ee4cb73 100644 --- a/src/role-helpers.js +++ b/src/role-helpers.js @@ -76,10 +76,4 @@ function logRoles(container) { .join('') } -export { - getRoles, - logRoles, - buildElementRoleList, - elementRoleList, - elementRoleMap, -} +export {getRoles, logRoles, elementRoleList, elementRoleMap} From aaf0254745387c7424a1eee41ea0cf79967b7299 Mon Sep 17 00:00:00 2001 From: mlasky Date: Sat, 15 Jun 2019 00:01:57 -0400 Subject: [PATCH 09/32] Removed lodash.merge dependency Lodash.merge is no longer required with the getRoles refactor --- package.json | 1 - 1 file changed, 1 deletion(-) diff --git a/package.json b/package.json index dda79a63..a4d0c662 100644 --- a/package.json +++ b/package.json @@ -45,7 +45,6 @@ "@babel/runtime": "^7.4.5", "@sheerun/mutationobserver-shim": "^0.3.2", "aria-query": "3.0.0", - "lodash.merge": "^4.6.1", "pretty-format": "^24.8.0", "wait-for-expect": "^1.2.0" }, From f362406858a30ddffa7c5e3547c1244d4e3923af Mon Sep 17 00:00:00 2001 From: mlasky Date: Sat, 15 Jun 2019 00:13:39 -0400 Subject: [PATCH 10/32] Minor: removed a useless -1 There was a leftover -1 on a calculation that calculates the number of '#'s for the role title in logRoles --- src/role-helpers.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/role-helpers.js b/src/role-helpers.js index 9ee4cb73..aea29241 100644 --- a/src/role-helpers.js +++ b/src/role-helpers.js @@ -65,7 +65,7 @@ function logRoles(container) { return Object.entries(roles) .map(([role, elements]) => { - const numDelimeters = 42 - role.length - 1 // 42 is arbitrary + const numDelimeters = 42 - role.length // 42 is arbitrary const delimeterBar = [...Array(numDelimeters)].map(_ => '#').join('') const elementsString = elements .map(el => `${debugDOM(el.cloneNode(false))}`) From cb21991b923c348fe2ef049d6334414adb2031d3 Mon Sep 17 00:00:00 2001 From: mlasky Date: Sat, 15 Jun 2019 00:21:41 -0400 Subject: [PATCH 11/32] Minor: statement no longer had to be temp literal --- src/role-helpers.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/role-helpers.js b/src/role-helpers.js index aea29241..c2eb134d 100644 --- a/src/role-helpers.js +++ b/src/role-helpers.js @@ -68,7 +68,7 @@ function logRoles(container) { const numDelimeters = 42 - role.length // 42 is arbitrary const delimeterBar = [...Array(numDelimeters)].map(_ => '#').join('') const elementsString = elements - .map(el => `${debugDOM(el.cloneNode(false))}`) + .map(el => debugDOM(el.cloneNode(false))) .join('\n\n') return `${role} ${delimeterBar}\n\n${elementsString}\n\n\n` From ee9597292fff063c35685c8c97dcc31f64be5056 Mon Sep 17 00:00:00 2001 From: mlasky Date: Sat, 15 Jun 2019 01:07:36 -0400 Subject: [PATCH 12/32] Reasability / sanity pass --- src/__tests__/role-helpers.js | 8 ++++---- src/role-helpers.js | 6 +++--- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/__tests__/role-helpers.js b/src/__tests__/role-helpers.js index 4fb71856..8af32175 100644 --- a/src/__tests__/role-helpers.js +++ b/src/__tests__/role-helpers.js @@ -59,7 +59,7 @@ function setup() { const td1 = getByTestId('a-cell-1') const td2 = getByTestId('a-cell-2') const td3 = getByTestId('a-cell-3') - const formEl = getByTestId('a-form') + const form = getByTestId('a-form') const input = getByTestId('a-input-1') const input2 = getByTestId('a-input-2') @@ -82,7 +82,7 @@ function setup() { td1, td2, td3, - formEl, + form, input, input2, } @@ -108,7 +108,7 @@ test('getRoles returns expected roles for various dom nodes', () => { td1, td2, td3, - formEl, + form, input, input2, } = setup() @@ -123,7 +123,7 @@ test('getRoles returns expected roles for various dom nodes', () => { table: [table], row: [tr], cell: [td1, td2, td3], - form: [formEl], + form: [form], textbox: [input, input2], rowgroup: [tbody], }) diff --git a/src/role-helpers.js b/src/role-helpers.js index c2eb134d..fb75afe8 100644 --- a/src/role-helpers.js +++ b/src/role-helpers.js @@ -46,7 +46,7 @@ function getRoles(container) { return [ node, ...Array.from(node.children).reduce( - (acc, c) => (c.tagName ? [...acc, ...flattenDOM(c)] : acc), + (acc, child) => (child.tagName ? [...acc, ...flattenDOM(child)] : acc), [], ), ] @@ -57,7 +57,7 @@ function getRoles(container) { return Array.isArray(acc[role]) ? {...acc, [role]: [...acc[role], node]} : {...acc, [role]: [node]} - }, []) + }, {}) } function logRoles(container) { @@ -66,7 +66,7 @@ function logRoles(container) { return Object.entries(roles) .map(([role, elements]) => { const numDelimeters = 42 - role.length // 42 is arbitrary - const delimeterBar = [...Array(numDelimeters)].map(_ => '#').join('') + const delimeterBar = '#'.repeat(numDelimeters) const elementsString = elements .map(el => debugDOM(el.cloneNode(false))) .join('\n\n') From fbd7e2bca581d549169ea276b15dad2cd22e9cc7 Mon Sep 17 00:00:00 2001 From: mlasky Date: Sat, 15 Jun 2019 01:37:30 -0400 Subject: [PATCH 13/32] Initial value for reducer switched to array --- src/role-helpers.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/role-helpers.js b/src/role-helpers.js index fb75afe8..0d3f5c73 100644 --- a/src/role-helpers.js +++ b/src/role-helpers.js @@ -57,7 +57,7 @@ function getRoles(container) { return Array.isArray(acc[role]) ? {...acc, [role]: [...acc[role], node]} : {...acc, [role]: [node]} - }, {}) + }, []) } function logRoles(container) { From 22dbce9561c7ec345cfc15f1fcfea2f80132cfa4 Mon Sep 17 00:00:00 2001 From: mlasky Date: Sat, 15 Jun 2019 02:06:14 -0400 Subject: [PATCH 14/32] Fixed bug with node selectors on specificity > 0 nodes Nodes that required greater specificity (ie input[type="radio"] is role radio, not the default textbox) where not getting their roles set correctly. Moved getImplicitAriaRole from src/queries/role to src/role-helpers --- src/__tests__/role-helpers.js | 15 ++++++++++++++- src/queries/role.js | 12 +----------- src/role-helpers.js | 34 +++++++++++++++++++++++++--------- 3 files changed, 40 insertions(+), 21 deletions(-) diff --git a/src/__tests__/role-helpers.js b/src/__tests__/role-helpers.js index 8af32175..463b2f51 100644 --- a/src/__tests__/role-helpers.js +++ b/src/__tests__/role-helpers.js @@ -29,8 +29,11 @@ function setup() {
+ + +
    @@ -60,8 +63,11 @@ function setup() { const td2 = getByTestId('a-cell-2') const td3 = getByTestId('a-cell-3') const form = getByTestId('a-form') + const radio = getByTestId('a-radio-1') + const radio2 = getByTestId('a-radio-2') const input = getByTestId('a-input-1') const input2 = getByTestId('a-input-2') + const textarea = getByTestId('a-textarea') return { section, @@ -83,8 +89,11 @@ function setup() { td2, td3, form, + radio, + radio2, input, input2, + textarea, } } @@ -109,14 +118,18 @@ test('getRoles returns expected roles for various dom nodes', () => { td2, td3, form, + radio, + radio2, input, input2, + textarea, } = setup() expect(getRoles(section)).toEqual({ region: [section], heading: [h1, h2, h3], navigation: [nav], + radio: [radio, radio2], article: [article], list: [aUl, bUl], listitem: [aLi1, aLi2, bLi1, bLi2], @@ -124,7 +137,7 @@ test('getRoles returns expected roles for various dom nodes', () => { row: [tr], cell: [td1, td2, td3], form: [form], - textbox: [input, input2], + textbox: [input, input2, textarea], rowgroup: [tbody], }) }) diff --git a/src/queries/role.js b/src/queries/role.js index e5a7fd5a..efa34be4 100644 --- a/src/queries/role.js +++ b/src/queries/role.js @@ -1,4 +1,4 @@ -import {elementRoleList} from '../role-helpers' +import {getImplicitAriaRole} from '../role-helpers' import {buildQueries, fuzzyMatches, makeNormalizer, matches} from './all-utils' function queryAllByRole( @@ -9,16 +9,6 @@ function queryAllByRole( const matcher = exact ? matches : fuzzyMatches const matchNormalizer = makeNormalizer({collapseWhitespace, trim, normalizer}) - function getImplicitAriaRole(currentNode) { - for (const {selector, roles} of elementRoleList) { - if (currentNode.matches(selector)) { - return [...roles] - } - } - - return [] - } - return Array.from(container.querySelectorAll('*')).filter(node => { const isRoleSpecifiedExplicitly = node.hasAttribute('role') diff --git a/src/role-helpers.js b/src/role-helpers.js index 0d3f5c73..cf577b26 100644 --- a/src/role-helpers.js +++ b/src/role-helpers.js @@ -1,6 +1,22 @@ import {elementRoles} from 'aria-query' import {debugDOM} from './query-helpers' +const elementRoleList = buildElementRoleList(elementRoles) +const elementRoleMap = elementRoleList.reduce( + (acc, {selector, roles}) => ({...acc, [selector]: roles}), + {}, +) + +function getImplicitAriaRole(currentNode) { + for (const {selector, roles} of elementRoleList) { + if (currentNode.matches(selector)) { + return [...roles] + } + } + + return [] +} + function buildElementRoleList(elementRolesMap) { function makeElementSelector({name, attributes = []}) { return `${name}${attributes @@ -35,12 +51,6 @@ function buildElementRoleList(elementRolesMap) { return result.sort(bySelectorSpecificity) } -const elementRoleList = buildElementRoleList(elementRoles) -const elementRoleMap = elementRoleList.reduce( - (acc, {selector, roles}) => ({...acc, [selector]: roles}), - {}, -) - function getRoles(container) { function flattenDOM(node) { return [ @@ -53,11 +63,11 @@ function getRoles(container) { } return flattenDOM(container).reduce((acc, node) => { - const role = elementRoleMap[node.tagName.toLowerCase()] + const [role] = getImplicitAriaRole(node) return Array.isArray(acc[role]) ? {...acc, [role]: [...acc[role], node]} : {...acc, [role]: [node]} - }, []) + }, {}) } function logRoles(container) { @@ -76,4 +86,10 @@ function logRoles(container) { .join('') } -export {getRoles, logRoles, elementRoleList, elementRoleMap} +export { + getRoles, + logRoles, + elementRoleList, + elementRoleMap, + getImplicitAriaRole, +} From 2814797fe248c899886bb2777c11fc25aa45457c Mon Sep 17 00:00:00 2001 From: mlasky Date: Sat, 15 Jun 2019 02:16:15 -0400 Subject: [PATCH 15/32] Added the role-helpers snapshot --- .../__snapshots__/role-helpers.js.snap | 144 ++++++++++++++++++ 1 file changed, 144 insertions(+) create mode 100644 src/__tests__/__snapshots__/role-helpers.js.snap diff --git a/src/__tests__/__snapshots__/role-helpers.js.snap b/src/__tests__/__snapshots__/role-helpers.js.snap new file mode 100644 index 00000000..ef89a8b2 --- /dev/null +++ b/src/__tests__/__snapshots__/role-helpers.js.snap @@ -0,0 +1,144 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`logRoles logs expected roles for various dom nodes 1`] = ` +"region #################################### + + + + +navigation ################################ + + + + +heading ################################### + + + + + + + + +article ################################### + + + + +list ###################################### + + + + + + +listitem ################################## + + + + + + + + + + +table ##################################### + + + + +rowgroup ################################## + + + + +row ####################################### + + + + +cell ###################################### + + + + + + + + +form ###################################### + + + + +radio ##################################### + + + + + + +textbox ################################### + + + + + + + + +" +`; From 7b5ecb1f1a338d06ebefa9722ce5d409aa952113 Mon Sep 17 00:00:00 2001 From: mlasky Date: Sat, 15 Jun 2019 02:41:24 -0400 Subject: [PATCH 16/32] Added basic test coverage for getImplicitAriaRole --- src/__tests__/role-helpers.js | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/src/__tests__/role-helpers.js b/src/__tests__/role-helpers.js index 463b2f51..9f6a154e 100644 --- a/src/__tests__/role-helpers.js +++ b/src/__tests__/role-helpers.js @@ -1,5 +1,4 @@ -/* eslint-disable no-control-regex */ -import {getRoles, logRoles} from '../role-helpers' +import {getRoles, logRoles, getImplicitAriaRole} from '../role-helpers' import {render} from './helpers/test-utils' function setup() { @@ -151,3 +150,13 @@ test('logRoles logs expected roles for various dom nodes', () => { //console.log(output); expect(output).toMatchSnapshot() }) + +test('getImplicitAriaRole returns expected roles for various dom nodes', () => { + const {section, h1, form, radio, input} = setup() + + expect(getImplicitAriaRole(section)).toEqual(['region']) + expect(getImplicitAriaRole(h1)).toEqual(['heading']) + expect(getImplicitAriaRole(form)).toEqual(['form']) + expect(getImplicitAriaRole(radio)).toEqual(['radio']) + expect(getImplicitAriaRole(input)).toEqual(['textbox']) +}) From e96cbbd5bcd47671e9516aa21b0959d2233b736c Mon Sep 17 00:00:00 2001 From: mlasky Date: Sat, 15 Jun 2019 03:21:42 -0400 Subject: [PATCH 17/32] Removed un-needed branch Also gives 100% branch coverage --- src/__tests__/role-helpers.js | 2 +- src/role-helpers.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/__tests__/role-helpers.js b/src/__tests__/role-helpers.js index 9f6a154e..2817e51b 100644 --- a/src/__tests__/role-helpers.js +++ b/src/__tests__/role-helpers.js @@ -11,7 +11,7 @@ function setup() {

    Tertiary Heading

    - +
    • Item 1
    • Item 2
    • diff --git a/src/role-helpers.js b/src/role-helpers.js index cf577b26..0d3f039c 100644 --- a/src/role-helpers.js +++ b/src/role-helpers.js @@ -56,7 +56,7 @@ function getRoles(container) { return [ node, ...Array.from(node.children).reduce( - (acc, child) => (child.tagName ? [...acc, ...flattenDOM(child)] : acc), + (acc, child) => [...acc, ...flattenDOM(child)], [], ), ] From afd42c0363e04c06e95f91cf353a6b5337181f8f Mon Sep 17 00:00:00 2001 From: mlasky Date: Sat, 15 Jun 2019 03:33:10 -0400 Subject: [PATCH 18/32] Minor: Added a cleanup afterEach test --- src/__tests__/role-helpers.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/__tests__/role-helpers.js b/src/__tests__/role-helpers.js index 2817e51b..13c08339 100644 --- a/src/__tests__/role-helpers.js +++ b/src/__tests__/role-helpers.js @@ -1,5 +1,7 @@ import {getRoles, logRoles, getImplicitAriaRole} from '../role-helpers' -import {render} from './helpers/test-utils' +import {render, cleanup} from './helpers/test-utils' + +afterEach(cleanup) function setup() { const {getByTestId} = render(` From 76c2d3fd3e0a92e90e66164dd2405c160794df1e Mon Sep 17 00:00:00 2001 From: mlasky Date: Sat, 15 Jun 2019 04:08:14 -0400 Subject: [PATCH 19/32] Removed unneeded elementRoleMap elementRoleMap was useful before getRoles was refactored, but is no longer being used. --- src/role-helpers.js | 12 +----------- 1 file changed, 1 insertion(+), 11 deletions(-) diff --git a/src/role-helpers.js b/src/role-helpers.js index 0d3f039c..40845ce1 100644 --- a/src/role-helpers.js +++ b/src/role-helpers.js @@ -2,10 +2,6 @@ import {elementRoles} from 'aria-query' import {debugDOM} from './query-helpers' const elementRoleList = buildElementRoleList(elementRoles) -const elementRoleMap = elementRoleList.reduce( - (acc, {selector, roles}) => ({...acc, [selector]: roles}), - {}, -) function getImplicitAriaRole(currentNode) { for (const {selector, roles} of elementRoleList) { @@ -86,10 +82,4 @@ function logRoles(container) { .join('') } -export { - getRoles, - logRoles, - elementRoleList, - elementRoleMap, - getImplicitAriaRole, -} +export {getRoles, logRoles, elementRoleList, getImplicitAriaRole} From a5a81c8f8b15afa7f219bb3489dd21872f975c22 Mon Sep 17 00:00:00 2001 From: mlasky Date: Mon, 17 Jun 2019 00:41:19 -0400 Subject: [PATCH 20/32] getRoles now handles muultiple implicit roles getRoles no longer assumes one implicit role for a given tag. getImplicitAriaRole was renamed to getImplicitAriaRoles Added a tag (menuitem) that has multiple imlicit roles to test case. --- .../__snapshots__/role-helpers.js.snap | 22 +++++++++++++ src/__tests__/role-helpers.js | 33 ++++++++++++++----- src/queries/role.js | 4 +-- src/role-helpers.js | 16 +++++---- 4 files changed, 58 insertions(+), 17 deletions(-) diff --git a/src/__tests__/__snapshots__/role-helpers.js.snap b/src/__tests__/__snapshots__/role-helpers.js.snap index ef89a8b2..82c675db 100644 --- a/src/__tests__/__snapshots__/role-helpers.js.snap +++ b/src/__tests__/__snapshots__/role-helpers.js.snap @@ -37,6 +37,28 @@ article ################################### /> +command ################################### + + + + + + +menuitem ################################## + + + + + + list ###################################### Main Heading

Sub Heading

Tertiary Heading

- +
+ + 1 + 2
  • Item 1
  • @@ -51,6 +58,8 @@ function setup() { const h3 = getByTestId('tertiary-heading') const nav = getByTestId('nav-bar') const article = getByTestId('featured-article') + const menuItem = getByTestId('a-menuitem-1') + const menuItem2 = getByTestId('a-menuitem-2') const aUl = getByTestId('a-list') const aLi1 = getByTestId('a-list-item-1') const aLi2 = getByTestId('a-list-item-2') @@ -77,6 +86,8 @@ function setup() { h3, nav, article, + menuItem, + menuItem2, aUl, aLi1, aLi2, @@ -106,6 +117,8 @@ test('getRoles returns expected roles for various dom nodes', () => { h3, nav, article, + menuItem, + menuItem2, aUl, aLi1, aLi2, @@ -140,6 +153,8 @@ test('getRoles returns expected roles for various dom nodes', () => { form: [form], textbox: [input, input2, textarea], rowgroup: [tbody], + command: [menuItem, menuItem2], + menuitem: [menuItem, menuItem2], }) }) @@ -149,16 +164,16 @@ test('logRoles logs expected roles for various dom nodes', () => { // If the snapshot needs to be updated, uncomment the console.log // and take a look at the output to make sure it still looks good - //console.log(output); + // console.log(output); expect(output).toMatchSnapshot() }) -test('getImplicitAriaRole returns expected roles for various dom nodes', () => { +test('getImplicitAriaRoles returns expected roles for various dom nodes', () => { const {section, h1, form, radio, input} = setup() - expect(getImplicitAriaRole(section)).toEqual(['region']) - expect(getImplicitAriaRole(h1)).toEqual(['heading']) - expect(getImplicitAriaRole(form)).toEqual(['form']) - expect(getImplicitAriaRole(radio)).toEqual(['radio']) - expect(getImplicitAriaRole(input)).toEqual(['textbox']) + expect(getImplicitAriaRoles(section)).toEqual(['region']) + expect(getImplicitAriaRoles(h1)).toEqual(['heading']) + expect(getImplicitAriaRoles(form)).toEqual(['form']) + expect(getImplicitAriaRoles(radio)).toEqual(['radio']) + expect(getImplicitAriaRoles(input)).toEqual(['textbox']) }) diff --git a/src/queries/role.js b/src/queries/role.js index efa34be4..45474211 100644 --- a/src/queries/role.js +++ b/src/queries/role.js @@ -1,4 +1,4 @@ -import {getImplicitAriaRole} from '../role-helpers' +import {getImplicitAriaRoles} from '../role-helpers' import {buildQueries, fuzzyMatches, makeNormalizer, matches} from './all-utils' function queryAllByRole( @@ -16,7 +16,7 @@ function queryAllByRole( return matcher(node.getAttribute('role'), node, role, matchNormalizer) } - const implicitRoles = getImplicitAriaRole(node) + const implicitRoles = getImplicitAriaRoles(node) return implicitRoles.some(implicitRole => matcher(implicitRole, node, role, matchNormalizer), diff --git a/src/role-helpers.js b/src/role-helpers.js index 40845ce1..36f3bacc 100644 --- a/src/role-helpers.js +++ b/src/role-helpers.js @@ -3,7 +3,7 @@ import {debugDOM} from './query-helpers' const elementRoleList = buildElementRoleList(elementRoles) -function getImplicitAriaRole(currentNode) { +function getImplicitAriaRoles(currentNode) { for (const {selector, roles} of elementRoleList) { if (currentNode.matches(selector)) { return [...roles] @@ -59,10 +59,14 @@ function getRoles(container) { } return flattenDOM(container).reduce((acc, node) => { - const [role] = getImplicitAriaRole(node) - return Array.isArray(acc[role]) - ? {...acc, [role]: [...acc[role], node]} - : {...acc, [role]: [node]} + const roles = getImplicitAriaRoles(node) + return roles.reduce( + (rolesAcc, role) => + Array.isArray(rolesAcc[role]) + ? {...rolesAcc, [role]: [...rolesAcc[role], node]} + : {...rolesAcc, [role]: [node]}, + acc, + ) }, {}) } @@ -82,4 +86,4 @@ function logRoles(container) { .join('') } -export {getRoles, logRoles, elementRoleList, getImplicitAriaRole} +export {getRoles, logRoles, elementRoleList, getImplicitAriaRoles} From 6ad8ef9b4ea201719000fc26b3e9f121558cadc5 Mon Sep 17 00:00:00 2001 From: mlasky Date: Mon, 17 Jun 2019 23:27:18 -0400 Subject: [PATCH 21/32] Added npm package jest-serializer-ansi --- package.json | 1 + 1 file changed, 1 insertion(+) diff --git a/package.json b/package.json index a4d0c662..045e16ab 100644 --- a/package.json +++ b/package.json @@ -52,6 +52,7 @@ "dtslint": "^0.7.7", "jest-dom": "^3.4.0", "jest-in-case": "^1.0.2", + "jest-serializer-ansi": "^1.0.3", "kcd-scripts": "^1.4.0" }, "eslintConfig": { From 02ab1b907bb55fd82a95ffea3910b37fe7af5c9d Mon Sep 17 00:00:00 2001 From: mlasky Date: Mon, 17 Jun 2019 23:28:19 -0400 Subject: [PATCH 22/32] Snapshot now uses jest-serilizer-ansi Allows us to strip the control characters that debugDOM embeds to change text colors on the console. --- .../__snapshots__/role-helpers.js.snap | 176 +++++++++--------- src/__tests__/role-helpers.js | 3 + src/role-helpers.js | 1 + 3 files changed, 92 insertions(+), 88 deletions(-) diff --git a/src/__tests__/__snapshots__/role-helpers.js.snap b/src/__tests__/__snapshots__/role-helpers.js.snap index 82c675db..7d715caa 100644 --- a/src/__tests__/__snapshots__/role-helpers.js.snap +++ b/src/__tests__/__snapshots__/role-helpers.js.snap @@ -3,163 +3,163 @@ exports[`logRoles logs expected roles for various dom nodes 1`] = ` "region #################################### - +
    navigation ################################ - +