From fbd30d802c49b2326ab6a6fd74646882b9f32515 Mon Sep 17 00:00:00 2001 From: Nic Townsend Date: Thu, 29 Apr 2021 16:32:30 +0100 Subject: [PATCH] Add keyboard handler and aria attributes to collapse/expand braces, strings and functions Fixes #355 --- src/js/components/DataTypes/Function.js | 4 +- src/js/components/DataTypes/Object.js | 10 +- src/js/components/DataTypes/String.js | 7 +- src/js/helpers/util.js | 17 +++ .../js/components/DataTypes/Function-test.js | 110 ++++++++++----- .../js/components/DataTypes/Object-test.js | 54 ++++++- .../js/components/DataTypes/String-test.js | 132 ++++++++++-------- 7 files changed, 229 insertions(+), 105 deletions(-) diff --git a/src/js/components/DataTypes/Function.js b/src/js/components/DataTypes/Function.js index 473afe9e..4f8dec69 100644 --- a/src/js/components/DataTypes/Function.js +++ b/src/js/components/DataTypes/Function.js @@ -4,6 +4,8 @@ import DataTypeLabel from './DataTypeLabel'; //theme import Theme from './../../themes/getStyle'; +import { generateExpanderProps } from '../../helpers/util'; + //attribute store for storing collapsed state import AttributeStore from './../../stores/ObjectAttributes'; @@ -48,7 +50,7 @@ export default class extends React.PureComponent { {this.getFunctionDisplay(collapsed)} diff --git a/src/js/components/DataTypes/Object.js b/src/js/components/DataTypes/Object.js index 4c7cb93f..ee23ab6b 100644 --- a/src/js/components/DataTypes/Object.js +++ b/src/js/components/DataTypes/Object.js @@ -1,6 +1,6 @@ import React from 'react'; import { polyfill } from 'react-lifecycles-compat'; -import { toType } from './../../helpers/util'; +import { toType, generateExpanderProps } from './../../helpers/util'; //data type components import { JsonObject } from './DataTypes'; @@ -156,10 +156,12 @@ class RjvObject extends React.PureComponent { return ( { - this.toggleCollapsed(); - }} + {...generateExpanderProps( + this.toggleCollapsed, + this.state.expanded + )} {...Theme(theme, 'brace-row')} + className="rjv-object-container" >
{quotesOnValues ? '"' : ''} {value} diff --git a/src/js/helpers/util.js b/src/js/helpers/util.js index 50ab5185..58d067e8 100644 --- a/src/js/helpers/util.js +++ b/src/js/helpers/util.js @@ -53,3 +53,20 @@ export function isTheme(theme) { } return false; } + +export function generateExpanderProps(callback, isExpanded) { + function handler(e) { + if (e && e.key && e.key != 'Enter') { + return; + } else { + return callback(e); + } + } + return { + onClick: callback, + onKeyDown: handler, + role: 'button', + tabIndex: '0', + 'aria-expanded': isExpanded + }; +} diff --git a/test/tests/js/components/DataTypes/Function-test.js b/test/tests/js/components/DataTypes/Function-test.js index 2207dd22..d127bb6a 100644 --- a/test/tests/js/components/DataTypes/Function-test.js +++ b/test/tests/js/components/DataTypes/Function-test.js @@ -1,86 +1,120 @@ -import React from "react" -import { shallow, mount } from "enzyme" -import { expect } from "chai" +import React from 'react'; +import { shallow, mount } from 'enzyme'; +import { expect } from 'chai'; -import JsonFunction from "./../../../../../src/js/components/DataTypes/Function" +import JsonFunction from './../../../../../src/js/components/DataTypes/Function'; -import AttributeStore from "./../../../../../src/js/stores/ObjectAttributes" +import AttributeStore from './../../../../../src/js/stores/ObjectAttributes'; -describe("", function() { - const rjvId = 1 +describe('', function () { + const rjvId = 1; - it("function component should have a data type label", function() { + it('function component should have a data type label', function () { const wrapper = mount( - ) - expect(wrapper.find(".data-type-label")).to.have.length(1) - }) + ); + expect(wrapper.find('.data-type-label')).to.have.length(1); + }); - it("function component should not have a data type label", function() { + it('function component should not have a data type label', function () { const wrapper = mount( - ) - expect(wrapper.find(".data-type-label")).to.have.length(0) - }) + ); + expect(wrapper.find('.data-type-label')).to.have.length(0); + }); - it("function component expanded", function() { - AttributeStore.set(rjvId, "function-test", "collapsed", false) + it('function component expanded', function () { + AttributeStore.set(rjvId, 'function-test', 'collapsed', false); const wrapper = shallow( - ) - expect(wrapper.find(".function-collapsed")).to.have.length(0) - }) + ); + expect(wrapper.find('.function-collapsed')).to.have.length(0); + }); - it("function component collapsed", function() { - AttributeStore.set(rjvId, "function-test", "collapsed", true) + it('function component collapsed', function () { + AttributeStore.set(rjvId, 'function-test', 'collapsed', true); const wrapper = shallow( - ) + ); - expect(wrapper.find(".function-collapsed")).to.have.length(1) - }) + expect(wrapper.find('.function-collapsed')).to.have.length(1); + }); - it("function component click to expand", function() { - AttributeStore.set(rjvId, "function-test", "collapsed", true) + it('function component click to expand', function () { + AttributeStore.set(rjvId, 'function-test', 'collapsed', true); const wrapper = shallow( - ) + ); - expect(wrapper.find(".function-collapsed")).to.have.length(1) + expect(wrapper.find('.function-collapsed')).to.have.length(1); + expect( + wrapper.find('.rjv-function-container').prop('aria-expanded') + ).to.equal(false); - wrapper.find(".rjv-function-container").simulate("click") + wrapper.find('.rjv-function-container').simulate('click'); - expect(wrapper.find(".function-collapsed")).to.have.length(0) - }) -}) + expect(wrapper.find('.function-collapsed')).to.have.length(0); + expect( + wrapper.find('.rjv-function-container').prop('aria-expanded') + ).to.equal(true); + }); + + it('function component keydown to expand', function () { + AttributeStore.set(rjvId, 'function-test', 'collapsed', true); + + const wrapper = shallow( + + ); + + expect(wrapper.find('.function-collapsed')).to.have.length(1); + expect( + wrapper.find('.rjv-function-container').prop('aria-expanded') + ).to.equal(false); + + wrapper + .find('.rjv-function-container') + .simulate('keydown', { key: 'Enter' }); + + expect(wrapper.find('.function-collapsed')).to.have.length(0); + expect( + wrapper.find('.rjv-function-container').prop('aria-expanded') + ).to.equal(true); + }); +}); diff --git a/test/tests/js/components/DataTypes/Object-test.js b/test/tests/js/components/DataTypes/Object-test.js index d50c8d7d..cf669248 100644 --- a/test/tests/js/components/DataTypes/Object-test.js +++ b/test/tests/js/components/DataTypes/Object-test.js @@ -5,7 +5,11 @@ import { expect } from 'chai'; import JsonObject from './../../../../../src/js/components/DataTypes/Object'; describe('', function () { - const rjvId = 1; + let rjvId = 1; + + beforeEach(() => { + rjvId++; + }); it('Object component should have a data type label', function () { let src = { @@ -373,4 +377,52 @@ describe('', function () { ); expect(wrapper.text()).to.equal('"":{"d":"d""b":"b""a":"a""c":"c"}'); }); + + it('Object click to expand', function () { + let src = { + obj: { + test: true + } + }; + const wrapper = shallow( + + ); + expect( + wrapper.find('.rjv-object-container').prop('aria-expanded') + ).to.equal(false); + wrapper.find('.rjv-object-container').simulate('click'); + expect( + wrapper.find('.rjv-object-container').prop('aria-expanded') + ).to.equal(true); + }); + + it('Object keydown to expand', function () { + let src = { + obj: { + test: true + } + }; + const wrapper = shallow( + + ); + + expect( + wrapper.find('.rjv-object-container').prop('aria-expanded') + ).to.equal(false); + + wrapper + .find('.rjv-object-container') + .simulate('keydown', { key: 'Space' }); + + expect( + wrapper.find('.rjv-object-container').prop('aria-expanded') + ).to.equal(false); + + wrapper + .find('.rjv-object-container') + .simulate('keydown', { key: 'Enter' }); + expect( + wrapper.find('.rjv-object-container').at(0).prop('aria-expanded') + ).to.equal(true); + }); }); diff --git a/test/tests/js/components/DataTypes/String-test.js b/test/tests/js/components/DataTypes/String-test.js index 184dc22a..bdd24e73 100644 --- a/test/tests/js/components/DataTypes/String-test.js +++ b/test/tests/js/components/DataTypes/String-test.js @@ -1,12 +1,12 @@ -import React from "react" -import { shallow, mount } from "enzyme" -import { expect } from "chai" +import React from 'react'; +import { shallow, mount } from 'enzyme'; +import { expect } from 'chai'; -import JsonString from "./../../../../../src/js/components/DataTypes/String" +import JsonString from './../../../../../src/js/components/DataTypes/String'; -describe("", function() { - it("string component should have a data type label", function() { - const rjvId = 1 +describe('', function () { + it('string component should have a data type label', function () { + const rjvId = 1; const wrapper = mount( ", function() { displayDataTypes={true} theme="rjv-default" /> - ) - expect(wrapper.find(".data-type-label")).to.have.length(1) - }) + ); + expect(wrapper.find('.data-type-label')).to.have.length(1); + }); - it("string with hidden data type", function() { - const rjvId = 1 + it('string with hidden data type', function () { + const rjvId = 1; const props = { - value: "test", + value: 'test', rjvId: 1, - theme: "rjv-default", + theme: 'rjv-default', displayDataTypes: false - } - const component = mount().render() - expect(component.find(".data-type-label")).to.have.length(0) - }) + }; + const component = mount().render(); + expect(component.find('.data-type-label')).to.have.length(0); + }); //test collapsed string and expand click - it("string displaying data type", function() { - const rjvId = 1 + it('string displaying data type', function () { + const rjvId = 1; const props = { - value: "test", + value: 'test', rjvId: 1, displayDataTypes: false, - theme: "rjv-default" - } - const component = mount().render() - expect(component.find(".data-type-label")).to.have.length(0) - }) + theme: 'rjv-default' + }; + const component = mount().render(); + expect(component.find('.data-type-label')).to.have.length(0); + }); - it("collapsed string content", function() { - const rjvId = 1 + it('collapsed string content', function () { const props = { - value: "123456789", + value: '123456789', collapseStringsAfterLength: 3, rjvId: 1, displayDataTypes: false, - theme: "rjv-default" - } - const component = shallow() - expect( - component - .render() - .find(".string-value") - .text() - ).to.equal('"123 ..."') - component.find(".string-value").simulate("click") - expect( - component - .render() - .find(".string-value") - .text() - ).to.equal('"123456789"') - }) + theme: 'rjv-default' + }; + const component = shallow(); + expect(component.render().find('.string-value').text()).to.equal( + '"123 ..."' + ); + component.find('.string-value').simulate('click'); + expect(component.render().find('.string-value').text()).to.equal( + '"123456789"' + ); + }); - it("remove quotes from string value", function() { + it('remove quotes from string value', function () { const props = { - value: "123456789", + value: '123456789', quotesOnValues: false, rjvId: 1, displayDataTypes: false, - theme: "rjv-default" - } - const component = shallow() - expect( - component - .render() - .find(".string-value") - .text() - ).to.equal('123456789') - }) -}) + theme: 'rjv-default' + }; + const component = shallow(); + expect(component.render().find('.string-value').text()).to.equal( + '123456789' + ); + }); + + it('collapsed string content with key handler', function () { + const props = { + value: '123456789', + collapseStringsAfterLength: 3, + rjvId: 2, + displayDataTypes: false, + theme: 'rjv-default' + }; + const component = shallow(); + expect(component.render().find('.string-value').text()).to.equal( + '"123 ..."' + ); + + component.find('.string-value').simulate('keydown', { key: 'Space' }); + expect(component.render().find('.string-value').text()).to.equal( + '"123 ..."' + ); + + component.find('.string-value').simulate('keydown', { key: 'Enter' }); + expect(component.render().find('.string-value').text()).to.equal( + '"123456789"' + ); + }); +});