@urbanos/react-discovery-ui 2.1.61 → 2.1.63

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (80) hide show
  1. package/lib/ReactDiscoveryUI.js +1 -1
  2. package/lib/components/auth0-login-zone/auth0-login-zone.test.js +11 -3
  3. package/lib/components/code-editor/code-editor.js +5 -5
  4. package/lib/components/collapsable-box/collapsable-box.js +10 -9
  5. package/lib/components/collapsable-box/collapsable-box.test.js +5 -10
  6. package/lib/components/data-card/data-card.js +13 -10
  7. package/lib/components/data-card/data-card.test.js +8 -0
  8. package/lib/components/data-view/data-view.js +3 -2
  9. package/lib/components/dataset-api-doc/api-example.js +3 -2
  10. package/lib/components/dataset-api-doc/dataset-api-doc.js +3 -2
  11. package/lib/components/dataset-api-doc/dataset-api-doc.test.js +1 -2
  12. package/lib/components/dataset-dictionary/dataset-dictionary.js +39 -8
  13. package/lib/components/dataset-dictionary/dataset-dictionary.scss +16 -0
  14. package/lib/components/dataset-dictionary/dataset-dictionary.test.js +50 -3
  15. package/lib/components/dataset-list/dataset-list.js +3 -2
  16. package/lib/components/dataset-metadata/dataset-metadata.js +32 -33
  17. package/lib/components/dataset-metadata/dataset-metadata.test.js +1 -1
  18. package/lib/components/dataset-preview/dataset-preview.js +12 -12
  19. package/lib/components/dataset-preview/dataset-preview.test.js +2 -3
  20. package/lib/components/dataset-quality/dataset-quality.js +8 -7
  21. package/lib/components/dataset-quality/dataset-quality.test.js +1 -2
  22. package/lib/components/dataset-recommendations/dataset-recommendations.js +2 -1
  23. package/lib/components/dataset-recommendations/dataset-recommendations.test.js +1 -1
  24. package/lib/components/detail-toggle-icon/detail-toggle-icon.js +8 -7
  25. package/lib/components/detail-toggle-icon/detail-toggle-icon.test.js +1 -1
  26. package/lib/components/facet-list/facet-list.test.js +1 -1
  27. package/lib/components/facet-sidebar/facet-sidebar.js +8 -7
  28. package/lib/components/generic-elements/arrow-left-button/arrow-left-button.js +3 -2
  29. package/lib/components/generic-elements/arrow-right-button/arrow-right-button.js +3 -2
  30. package/lib/components/generic-elements/auto-anchoring-popover/index.js +7 -9
  31. package/lib/components/generic-elements/back-button/back-button.js +3 -2
  32. package/lib/components/generic-elements/call-to-action-button/call-to-action-button.test.js +2 -2
  33. package/lib/components/generic-elements/chart-icon/index.js +3 -2
  34. package/lib/components/generic-elements/folder-icon/index.js +3 -2
  35. package/lib/components/generic-elements/paginator/paginator.js +3 -2
  36. package/lib/components/generic-elements/plus-icon/index.js +3 -2
  37. package/lib/components/generic-elements/search/search.js +8 -7
  38. package/lib/components/generic-elements/select/select.js +3 -2
  39. package/lib/components/generic-elements/sql-icon/index.js +3 -2
  40. package/lib/components/generic-elements/tab-button/tab-button.js +4 -4
  41. package/lib/components/generic-elements/visualize-button/visualize-button.js +3 -2
  42. package/lib/components/login-zone/login-svgs-and-text.js +8 -7
  43. package/lib/components/login-zone/login.js +8 -7
  44. package/lib/components/login-zone/logout.js +8 -7
  45. package/lib/components/network-loading-element/network-loading-element.js +3 -2
  46. package/lib/components/organization/organization.js +3 -2
  47. package/lib/components/query-form/query-form.js +2 -2
  48. package/lib/components/query-form/query-form.test.js +2 -1
  49. package/lib/components/recommendation-list/recommendation-list.test.js +1 -1
  50. package/lib/components/share/share.js +18 -18
  51. package/lib/components/streaming-api-doc/streaming-api-doc.js +8 -7
  52. package/lib/components/streaming-api-doc/streaming-api-doc.test.js +1 -2
  53. package/lib/components/tooltip/tooltip.js +5 -6
  54. package/lib/components/visualization-list-menu-item/visualization-list-menu-item.js +0 -1
  55. package/lib/components/visualizations/geojson/geojson-visualization.js +22 -20
  56. package/lib/components/visualizations/geojson/geojson-visualization.test.js +16 -16
  57. package/lib/pages/apikey-view/apikey-view.js +1 -1
  58. package/lib/pages/chart-view/chart-view.js +7 -7
  59. package/lib/pages/chart-view/chart-view.test.js +1 -1
  60. package/lib/pages/dataset-detail-view/dataset-detail-view-connector.test.js +1 -2
  61. package/lib/pages/dataset-detail-view/dataset-detail-view.js +8 -7
  62. package/lib/pages/dataset-list-view/dataset-list-view.test.js +1 -1
  63. package/lib/pages/dataset-view/dataset-view-connector.test.js +1 -2
  64. package/lib/pages/dataset-view/dataset-view.js +35 -35
  65. package/lib/pages/dataset-view/dataset-view.test.js +1 -1
  66. package/lib/pages/oauth-error-view/oauth-error-view.js +0 -1
  67. package/lib/pages/oauth-view/oauth-view.js +6 -4
  68. package/lib/pages/query-view/query-view.js +9 -12
  69. package/lib/pages/query-view/query-view.test.js +1 -0
  70. package/lib/pages/user-profile-view/user-profile-view.js +31 -33
  71. package/lib/pages/visualization-view/visualization-view.js +18 -20
  72. package/lib/pages/visualization-view/visualization-view.test.js +1 -0
  73. package/lib/search-params/search-params-manager.js +12 -12
  74. package/lib/search-params/search-params-manager.test.js +6 -6
  75. package/lib/store/reducers/reducers.js +17 -44
  76. package/lib/store/reducers/visualization-reducer.test.js +19 -9
  77. package/lib/store/sagas/logout-saga.js +3 -5
  78. package/lib/store/sagas/oauth-saga.test.js +0 -1
  79. package/lib/store/sagas/visualization-saga.js +2 -5
  80. package/package.json +8 -5
@@ -44,7 +44,7 @@ var noMatch = function noMatch() {
44
44
  };
45
45
  var DiscoveryUI = function DiscoveryUI() {
46
46
  return /*#__PURE__*/_react.default.createElement("section", {
47
- class: "main-app-element"
47
+ className: "main-app-element"
48
48
  }, /*#__PURE__*/_react.default.createElement(_networkLoadingElement.default, null), /*#__PURE__*/_react.default.createElement(_reactRouterDom.BrowserRouter, null, /*#__PURE__*/_react.default.createElement(_reactRouterDom.Switch, null, /*#__PURE__*/_react.default.createElement(_protectedRoute.default, {
49
49
  exact: true,
50
50
  path: _routes.default.root,
@@ -2,12 +2,20 @@ import { mount } from 'enzyme'
2
2
  import { Auth0LoginZone as Component } from './auth0-login-zone'
3
3
  import LoadingElement from '../generic-elements/loading-element'
4
4
  import { BrowserRouter as Router } from 'react-router-dom'
5
- import * as device from 'react-device-detect'
5
+
6
+ let mockIsMobile = false
7
+ jest.mock('react-device-detect', () => ({
8
+ get isMobile() {
9
+ return mockIsMobile
10
+ }
11
+ }))
12
+
6
13
 
7
14
  describe('OauthLoginZone component', () => {
8
15
  let subject, button, loginHandler, logoutHandler
9
16
 
10
17
  beforeEach(() => {
18
+ mockIsMobile = false
11
19
  loginHandler = jest.fn()
12
20
  logoutHandler = jest.fn()
13
21
  })
@@ -36,7 +44,7 @@ describe('OauthLoginZone component', () => {
36
44
 
37
45
  describe('authenticated', () => {
38
46
  beforeEach(() => {
39
- device.isMobile = false
47
+ mockIsMobile = false
40
48
  subject = createSubject({ isAuthenticated: true, loginWithRedirect: loginHandler, logout: logoutHandler })
41
49
  button = subject.find('button')
42
50
  })
@@ -114,7 +122,7 @@ describe('OauthLoginZone component', () => {
114
122
 
115
123
  describe('account menu on mobile', () => {
116
124
  beforeEach(() => {
117
- device.isMobile = true
125
+ mockIsMobile = true
118
126
  subject = createSubject({ isAuthenticated: true, loginWithRedirect: loginHandler, logout: logoutHandler })
119
127
  button = subject.find('button')
120
128
  })
@@ -5,7 +5,7 @@ Object.defineProperty(exports, "__esModule", {
5
5
  value: true
6
6
  });
7
7
  exports.default = void 0;
8
- var _react = _interopRequireDefault(require("react"));
8
+ var _react = require("react");
9
9
  var _reactSimpleCodeEditor = _interopRequireDefault(require("react-simple-code-editor"));
10
10
  var _prismCore = require("prismjs/components/prism-core");
11
11
  require("prismjs/components/prism-sql");
@@ -24,7 +24,7 @@ function _getPrototypeOf(t) { return _getPrototypeOf = Object.setPrototypeOf ? O
24
24
  function _inherits(t, e) { if ("function" != typeof e && null !== e) throw new TypeError("Super expression must either be null or a function"); t.prototype = Object.create(e && e.prototype, { constructor: { value: t, writable: !0, configurable: !0 } }), Object.defineProperty(t, "prototype", { writable: !1 }), e && _setPrototypeOf(t, e); }
25
25
  function _setPrototypeOf(t, e) { return _setPrototypeOf = Object.setPrototypeOf ? Object.setPrototypeOf.bind() : function (t, e) { return t.__proto__ = e, t; }, _setPrototypeOf(t, e); }
26
26
  var code = 'SELECT * FROM ...';
27
- var CodeEditor = /*#__PURE__*/function (_React$Component) {
27
+ var CodeEditor = /*#__PURE__*/function (_Component) {
28
28
  function CodeEditor(props) {
29
29
  var _this;
30
30
  _classCallCheck(this, CodeEditor);
@@ -34,12 +34,12 @@ var CodeEditor = /*#__PURE__*/function (_React$Component) {
34
34
  };
35
35
  return _this;
36
36
  }
37
- _inherits(CodeEditor, _React$Component);
37
+ _inherits(CodeEditor, _Component);
38
38
  return _createClass(CodeEditor, [{
39
39
  key: "render",
40
40
  value: function render() {
41
41
  var _this2 = this;
42
- return /*#__PURE__*/_react.default.createElement(_reactSimpleCodeEditor.default, {
42
+ return /*#__PURE__*/React.createElement(_reactSimpleCodeEditor.default, {
43
43
  value: this.state.code,
44
44
  onValueChange: function onValueChange(code) {
45
45
  return _this2.setState({
@@ -61,5 +61,5 @@ var CodeEditor = /*#__PURE__*/function (_React$Component) {
61
61
  });
62
62
  }
63
63
  }]);
64
- }(_react.default.Component);
64
+ }(_react.Component);
65
65
  var _default = exports.default = CodeEditor;
@@ -30,11 +30,11 @@ function extractText(node) {
30
30
  if (node.props && node.props.children) return extractText(node.props.children);
31
31
  return '';
32
32
  }
33
- var _default = exports.default = /*#__PURE__*/function (_Component) {
34
- function _default(props) {
33
+ var CollapsableBox = /*#__PURE__*/function (_Component) {
34
+ function CollapsableBox(props) {
35
35
  var _this;
36
- _classCallCheck(this, _default);
37
- _this = _callSuper(this, _default, [props]);
36
+ _classCallCheck(this, CollapsableBox);
37
+ _this = _callSuper(this, CollapsableBox, [props]);
38
38
  var isDesktop = window.matchMedia(_variables.default.aboveMaxBreak).matches;
39
39
  _this.state = {
40
40
  expanded: isDesktop || props.expanded
@@ -44,8 +44,8 @@ var _default = exports.default = /*#__PURE__*/function (_Component) {
44
44
  _this.contentId = "collapsable-box-content-".concat(normalizedTitle || 'content');
45
45
  return _this;
46
46
  }
47
- _inherits(_default, _Component);
48
- return _createClass(_default, [{
47
+ _inherits(CollapsableBox, _Component);
48
+ return _createClass(CollapsableBox, [{
49
49
  key: "toggleCollapsed",
50
50
  value: function toggleCollapsed() {
51
51
  this.setState({
@@ -72,8 +72,8 @@ var _default = exports.default = /*#__PURE__*/function (_Component) {
72
72
  }, /*#__PURE__*/React.createElement("div", {
73
73
  "data-testid": this.props.testId,
74
74
  className: "header-container ".concat(this.headerOpenClass()),
75
- onClick: function onClick(e) {
76
- _this2.toggleCollapsed();
75
+ onClick: function onClick() {
76
+ return _this2.toggleCollapsed();
77
77
  },
78
78
  onKeyDown: function onKeyDown(event) {
79
79
  if (event.key === ' ' || event.key === 'Enter') {
@@ -101,4 +101,5 @@ var _default = exports.default = /*#__PURE__*/function (_Component) {
101
101
  }, childrenWithContentId)));
102
102
  }
103
103
  }]);
104
- }(_react.Component);
104
+ }(_react.Component);
105
+ var _default = exports.default = CollapsableBox;
@@ -3,10 +3,9 @@ import CollapsableBox from './collapsable-box'
3
3
 
4
4
  describe('CollapsableBox ', () => {
5
5
  test('desktop default to expanded', () => {
6
- const matchMedia = jest.fn()
7
6
  Object.defineProperty(window, 'matchMedia', {
8
7
  writable: true,
9
- value: jest.fn().mockImplementation(query => ({
8
+ value: jest.fn().mockImplementation(() => ({
10
9
  matches: true
11
10
  }))
12
11
  })
@@ -19,10 +18,9 @@ describe('CollapsableBox ', () => {
19
18
  })
20
19
 
21
20
  test('mobile/table default to collapsed', () => {
22
- const matchMedia = jest.fn()
23
21
  Object.defineProperty(window, 'matchMedia', {
24
22
  writable: true,
25
- value: jest.fn().mockImplementation(query => ({
23
+ value: jest.fn().mockImplementation(() => ({
26
24
  matches: false
27
25
  }))
28
26
  })
@@ -35,10 +33,9 @@ describe('CollapsableBox ', () => {
35
33
  })
36
34
 
37
35
  test('clicking the header changes the expanded state on mobile/tablet', () => {
38
- const matchMedia = jest.fn()
39
36
  Object.defineProperty(window, 'matchMedia', {
40
37
  writable: true,
41
- value: jest.fn().mockImplementation(query => ({
38
+ value: jest.fn().mockImplementation(() => ({
42
39
  matches: false
43
40
  }))
44
41
  })
@@ -52,10 +49,9 @@ describe('CollapsableBox ', () => {
52
49
  })
53
50
 
54
51
  test('clicking the header changes the expanded state on Desktop', () => {
55
- const matchMedia = jest.fn()
56
52
  Object.defineProperty(window, 'matchMedia', {
57
53
  writable: true,
58
- value: jest.fn().mockImplementation(query => ({
54
+ value: jest.fn().mockImplementation(() => ({
59
55
  matches: true
60
56
  }))
61
57
  })
@@ -69,10 +65,9 @@ describe('CollapsableBox ', () => {
69
65
  })
70
66
 
71
67
  test('clicking the header changes the expanded state on expanded', () => {
72
- const matchMedia = jest.fn()
73
68
  Object.defineProperty(window, 'matchMedia', {
74
69
  writable: true,
75
- value: jest.fn().mockImplementation(query => ({
70
+ value: jest.fn().mockImplementation(() => ({
76
71
  matches: undefined
77
72
  }))
78
73
  })
@@ -4,6 +4,7 @@ Object.defineProperty(exports, "__esModule", {
4
4
  value: true
5
5
  });
6
6
  exports.default = void 0;
7
+ var _react = require("react");
7
8
  require("./data-card.scss");
8
9
  var _reactRouterDom = require("react-router-dom");
9
10
  var _reactSanitizedHtml = _interopRequireDefault(require("react-sanitized-html"));
@@ -29,14 +30,16 @@ var DataCard = function DataCard(props) {
29
30
  });
30
31
  };
31
32
  var dataset = props.dataset;
32
- var _React$useState = React.useState('loading'),
33
- _React$useState2 = _slicedToArray(_React$useState, 2),
34
- imageStatus = _React$useState2[0],
35
- setImageStatus = _React$useState2[1];
36
- var logoAltText = function logoAltText() {
37
- if (imageStatus === 'fallback') return "No image available for ".concat(dataset.organization_title);
33
+ var _useState = (0, _react.useState)(null),
34
+ _useState2 = _slicedToArray(_useState, 2),
35
+ failedImageUrl = _useState2[0],
36
+ setFailedImageUrl = _useState2[1];
37
+ var logoAltText = function () {
38
+ if (failedImageUrl === dataset.organization_image_url) {
39
+ return "No image available for ".concat(dataset.organization_title);
40
+ }
38
41
  return "The logo for ".concat(dataset.organization_title);
39
- };
42
+ }();
40
43
  var truncatedDescription = truncateDescription(dataset.description, maxDescriptionLength);
41
44
  return /*#__PURE__*/React.createElement("data-card", null, /*#__PURE__*/React.createElement("div", {
42
45
  className: "logo"
@@ -46,9 +49,9 @@ var DataCard = function DataCard(props) {
46
49
  src: dataset.organization_image_url,
47
50
  fallbackImage: _no_image.default,
48
51
  initialImage: /*#__PURE__*/React.createElement(_loadingElement.default, null),
49
- alt: logoAltText(imageStatus, dataset),
50
- onError: function onError(errorUrl) {
51
- if (errorUrl === dataset.organization_image_url) setImageStatus('fallback');
52
+ alt: logoAltText,
53
+ onError: function onError() {
54
+ return setFailedImageUrl(dataset.organization_image_url);
52
55
  }
53
56
  }))), /*#__PURE__*/React.createElement("div", {
54
57
  className: "details"
@@ -47,6 +47,14 @@ describe('data card element with logo', () => {
47
47
  expect(subject.find(ReactImageFallback).prop('alt')).toEqual('The logo for Organization Title')
48
48
  expect(subject.find(ReactImageFallback).prop('src')).toEqual(dataset.organization_image_url)
49
49
  })
50
+
51
+ test('card to render fallback alt text when image fails to load', () => {
52
+ subject.find(ReactImageFallback).prop('onError')()
53
+
54
+ expect(subject.find(ReactImageFallback).prop('alt')).toEqual(
55
+ 'No image available for Organization Title'
56
+ )
57
+ })
50
58
  })
51
59
 
52
60
  describe('data card element with html description', () => {
@@ -334,7 +334,7 @@ var cleanseData = function cleanseData(data) {
334
334
  return cleanseRow(row);
335
335
  });
336
336
  };
337
- var _default = exports.default = function _default(props) {
337
+ var DataView = function DataView(props) {
338
338
  var _useState7 = (0, _react.useState)(0),
339
339
  _useState8 = _slicedToArray(_useState7, 2),
340
340
  index = _useState8[0],
@@ -389,4 +389,5 @@ var _default = exports.default = function _default(props) {
389
389
  theme: getTheme(),
390
390
  collapsed: 2
391
391
  })))));
392
- };
392
+ };
393
+ var _default = exports.default = DataView;
@@ -13,7 +13,7 @@ var defaultHeaders = [{
13
13
  description: 'Include your unique api key as the value. You can generate your key by navigating to the “API Key” item on the “My Account” menu.',
14
14
  required: false
15
15
  }];
16
- var _default = exports.default = function _default(_ref) {
16
+ var ApiExample = function ApiExample(_ref) {
17
17
  var title = _ref.title,
18
18
  descriptionHtml = _ref.descriptionHtml,
19
19
  action = _ref.action,
@@ -118,4 +118,5 @@ function renderExample(example, index) {
118
118
  }
119
119
  function createCurlCommand(example, url) {
120
120
  return "curl -X POST '".concat(url, "' -H 'Content-Type: text/plain' -H 'api_key: USER_API_KEY_HERE' -d '").concat(example.body, "'");
121
- }
121
+ }
122
+ var _default = exports.default = ApiExample;
@@ -107,7 +107,7 @@ function renderExamples(dataset) {
107
107
  headers: apiHeaders
108
108
  }));
109
109
  }
110
- var _default = exports.default = function _default(_ref) {
110
+ var DatasetApiDoc = function DatasetApiDoc(_ref) {
111
111
  var expanded = _ref.expanded,
112
112
  dataset = _ref.dataset;
113
113
  return /*#__PURE__*/React.createElement("dataset-api-doc", {
@@ -117,4 +117,5 @@ var _default = exports.default = function _default(_ref) {
117
117
  headerHtml: renderHeader(),
118
118
  expanded: expanded
119
119
  }, renderExamples(dataset)));
120
- };
120
+ };
121
+ var _default = exports.default = DatasetApiDoc;
@@ -13,10 +13,9 @@ describe('dataset api doc ', () => {
13
13
  let subject
14
14
 
15
15
  beforeEach(() => {
16
- const matchMedia = jest.fn()
17
16
  Object.defineProperty(window, 'matchMedia', {
18
17
  writable: true,
19
- value: jest.fn().mockImplementation(query => ({
18
+ value: jest.fn().mockImplementation(() => ({
20
19
  matches: true
21
20
  }))
22
21
  })
@@ -21,6 +21,9 @@ function _arrayWithHoles(r) { if (Array.isArray(r)) return r; }
21
21
  var expanderWidth = 35;
22
22
  var expandedArrow = "\u25BE";
23
23
  var collapsedArrow = "\u25B8";
24
+ var expandedContentId = function expandedContentId(rowId) {
25
+ return "schema-row-".concat(String(rowId).replace(/[^a-zA-Z0-9_-]/g, '-'), "-content");
26
+ };
24
27
  var isMap = function isMap(schemaElement) {
25
28
  return schemaElement.type === 'map' || schemaElement.itemType === 'map';
26
29
  };
@@ -39,6 +42,10 @@ var schemaColumns = [{
39
42
  var fieldName = getValue();
40
43
  var rowIsMap = isMap(row.original);
41
44
  var isExpanded = row.getIsExpanded();
45
+ var handleToggleExpanded = row.getToggleExpandedHandler();
46
+ var toggleExpanded = function toggleExpanded() {
47
+ return row.toggleExpanded();
48
+ };
42
49
  return /*#__PURE__*/_react.default.createElement("div", {
43
50
  className: "field-cell-content"
44
51
  }, /*#__PURE__*/_react.default.createElement(_tooltip.default, {
@@ -46,8 +53,16 @@ var schemaColumns = [{
46
53
  }), rowIsMap && /*#__PURE__*/_react.default.createElement("button", {
47
54
  type: "button",
48
55
  className: "field-expander-button",
49
- onClick: row.getToggleExpandedHandler(),
56
+ onClick: handleToggleExpanded,
57
+ onKeyDown: function onKeyDown(event) {
58
+ if (event.key === ' ') {
59
+ event.preventDefault();
60
+ event.stopPropagation();
61
+ toggleExpanded();
62
+ }
63
+ },
50
64
  "aria-expanded": isExpanded,
65
+ "aria-controls": expandedContentId(row.id),
51
66
  "aria-label": "".concat(isExpanded ? 'Collapse' : 'Expand', " ").concat(fieldName)
52
67
  }, isExpanded ? expandedArrow : collapsedArrow));
53
68
  }
@@ -118,18 +133,32 @@ var _SchemaTable = function SchemaTable(_ref3) {
118
133
  key: headerGroup.id
119
134
  }, headerGroup.headers.map(function (header) {
120
135
  var _header$column$column;
136
+ var canSort = header.column.getCanSort();
137
+ var isSorted = header.column.getIsSorted();
138
+ var toggleSort = header.column.getToggleSortingHandler();
139
+ var headerLabel = String(header.column.columnDef.header);
121
140
  return /*#__PURE__*/_react.default.createElement("th", {
122
141
  key: header.id,
123
142
  "aria-label": header.id,
124
143
  scope: "col",
125
144
  className: ((_header$column$column = header.column.columnDef.meta) === null || _header$column$column === void 0 ? void 0 : _header$column$column.headerClassName) || 'table-header',
126
145
  style: {
127
- width: header.column.getSize() !== 150 ? "".concat(header.column.getSize(), "px") : undefined,
128
- cursor: header.column.getCanSort() ? 'pointer' : undefined
146
+ width: header.column.getSize() !== 150 ? "".concat(header.column.getSize(), "px") : undefined
147
+ },
148
+ "aria-sort": canSort ? isSorted === 'asc' ? 'ascending' : isSorted === 'desc' ? 'descending' : 'none' : undefined
149
+ }, header.isPlaceholder ? null : canSort ? /*#__PURE__*/_react.default.createElement("button", {
150
+ type: "button",
151
+ className: "schema-header-sort-button",
152
+ onClick: toggleSort,
153
+ onKeyDown: function onKeyDown(event) {
154
+ if (event.key === ' ') {
155
+ event.preventDefault();
156
+ event.stopPropagation();
157
+ toggleSort(event);
158
+ }
129
159
  },
130
- onClick: header.column.getCanSort() ? header.column.getToggleSortingHandler() : undefined,
131
- "aria-sort": header.column.getIsSorted() === 'asc' ? 'ascending' : header.column.getIsSorted() === 'desc' ? 'descending' : undefined
132
- }, header.isPlaceholder ? null : (0, _reactTable.flexRender)(header.column.columnDef.header, header.getContext()), header.column.getIsSorted() === 'asc' ? ' ↑' : header.column.getIsSorted() === 'desc' ? ' ↓' : '');
160
+ "aria-label": "Sort by ".concat(headerLabel)
161
+ }, (0, _reactTable.flexRender)(header.column.columnDef.header, header.getContext()), isSorted === 'asc' ? '' : isSorted === 'desc' ? ' ↓' : '') : /*#__PURE__*/_react.default.createElement(_react.default.Fragment, null, (0, _reactTable.flexRender)(header.column.columnDef.header, header.getContext()), isSorted === 'asc' ? ' ↑' : isSorted === 'desc' ? '' : ''));
133
162
  }));
134
163
  })), /*#__PURE__*/_react.default.createElement("tbody", null, table.getRowModel().rows.map(function (row) {
135
164
  return /*#__PURE__*/_react.default.createElement(_react.default.Fragment, {
@@ -141,6 +170,7 @@ var _SchemaTable = function SchemaTable(_ref3) {
141
170
  className: ((_cell$column$columnDe = cell.column.columnDef.meta) === null || _cell$column$columnDe === void 0 ? void 0 : _cell$column$columnDe.className) || ''
142
171
  }, (0, _reactTable.flexRender)(cell.column.columnDef.cell, cell.getContext()));
143
172
  })), row.getIsExpanded() && /*#__PURE__*/_react.default.createElement("tr", null, /*#__PURE__*/_react.default.createElement("td", {
173
+ id: expandedContentId(row.id),
144
174
  colSpan: schemaColumns.length
145
175
  }, /*#__PURE__*/_react.default.createElement(_SchemaTable, {
146
176
  schema: row.original.subSchema,
@@ -163,7 +193,7 @@ var viewLink = function viewLink(datasetId) {
163
193
  className: "view-text"
164
194
  }, "View as JSON")));
165
195
  };
166
- var _default = exports.default = function _default(_ref4) {
196
+ var DatasetDictionary = function DatasetDictionary(_ref4) {
167
197
  var schema = _ref4.schema,
168
198
  datasetId = _ref4.datasetId,
169
199
  _ref4$expanded = _ref4.expanded,
@@ -180,4 +210,5 @@ var _default = exports.default = function _default(_ref4) {
180
210
  }, !isEmpty(schema) && /*#__PURE__*/_react.default.createElement("div", null, /*#__PURE__*/_react.default.createElement(_SchemaTable, {
181
211
  schema: schema
182
212
  }), viewLink(datasetId))));
183
- };
213
+ };
214
+ var _default = exports.default = DatasetDictionary;
@@ -30,6 +30,22 @@ dataset-dictionary {
30
30
  font-weight: 800;
31
31
  }
32
32
 
33
+ .schema-header-sort-button {
34
+ border: 0;
35
+ background: transparent;
36
+ padding: 0;
37
+ font: inherit;
38
+ font-weight: inherit;
39
+ color: inherit;
40
+ cursor: pointer;
41
+ text-align: inherit;
42
+ }
43
+
44
+ .schema-header-sort-button:focus-visible {
45
+ outline: 2px solid $dark-blue;
46
+ outline-offset: 2px;
47
+ }
48
+
33
49
  .description-cell {
34
50
  white-space: unset;
35
51
  }
@@ -15,7 +15,7 @@ describe('dataset dictionary', () => {
15
15
  beforeEach(() => {
16
16
  Object.defineProperty(window, 'matchMedia', {
17
17
  writable: true,
18
- value: jest.fn().mockImplementation(query => ({
18
+ value: jest.fn().mockImplementation(() => ({
19
19
  matches: true
20
20
  }))
21
21
  })
@@ -49,8 +49,34 @@ describe('dataset dictionary', () => {
49
49
  })
50
50
 
51
51
  it('is sortable via column headers', () => {
52
- const headers = subject.find('.dataset-schema-table th')
53
- expect(headers.at(0).prop('onClick')).toBeTruthy()
52
+ const sortButtons = subject.find('.dataset-schema-table th .schema-header-sort-button')
53
+ expect(sortButtons.length).toBe(3)
54
+ expect(sortButtons.at(0).prop('type')).toBe('button')
55
+ })
56
+ it('sorts rows when a sort button is activated', () => {
57
+ const getFieldNames = () => subject.find('.dataset-schema-table tbody tr td:first-child').map(cell => cell.find(Tooltip).prop('text'))
58
+
59
+ expect(getFieldNames()).toEqual(['name', 'age'])
60
+
61
+ const fieldSortButton = subject.find('.dataset-schema-table th .schema-header-sort-button').at(0)
62
+ fieldSortButton.simulate('click')
63
+
64
+ expect(getFieldNames()).toEqual(['age', 'name'])
65
+ })
66
+
67
+ it('sorts rows when a sort button is activated with space', () => {
68
+ const getFieldNames = () => subject.find('.dataset-schema-table tbody tr td:first-child').map(cell => cell.find(Tooltip).prop('text'))
69
+ const preventDefault = jest.fn()
70
+ const stopPropagation = jest.fn()
71
+
72
+ expect(getFieldNames()).toEqual(['name', 'age'])
73
+
74
+ const fieldSortButton = subject.find('.dataset-schema-table th .schema-header-sort-button').at(0)
75
+ fieldSortButton.simulate('keydown', { key: ' ', preventDefault, stopPropagation })
76
+
77
+ expect(preventDefault).toHaveBeenCalledTimes(1)
78
+ expect(stopPropagation).toHaveBeenCalledTimes(1)
79
+ expect(getFieldNames()).toEqual(['age', 'name'])
54
80
  })
55
81
 
56
82
  it('has no left margin on the top-level table', () => {
@@ -115,6 +141,19 @@ describe('dataset dictionary', () => {
115
141
  expect(topLevelTable.find('.field-expander-button').at(0).text()).toBe('▸')
116
142
  })
117
143
 
144
+ it('expands map rows when the expander button is activated with space', () => {
145
+ const expanderButton = topLevelTable.find('.field-expander-button').at(0)
146
+ expect(subject.find('.dataset-schema-table.mother').length).toBe(0)
147
+
148
+ const preventDefault = jest.fn()
149
+ const stopPropagation = jest.fn()
150
+ expanderButton.simulate('keydown', { key: ' ', preventDefault, stopPropagation })
151
+
152
+ expect(preventDefault).toHaveBeenCalledTimes(1)
153
+ expect(stopPropagation).toHaveBeenCalledTimes(1)
154
+ expect(subject.find('.dataset-schema-table.mother').length).toBe(1)
155
+ })
156
+
118
157
  describe('with the map type expanded', () => {
119
158
  let subTable, subTableCells
120
159
 
@@ -130,6 +169,14 @@ describe('dataset dictionary', () => {
130
169
  expect(topLevelTable.find('.field-expander-button').at(0).text()).toBe('▾')
131
170
  })
132
171
 
172
+ it('connects expander controls to expanded content', () => {
173
+ const expanderButton = topLevelTable.find('.field-expander-button').at(0)
174
+ const controlsId = expanderButton.prop('aria-controls')
175
+
176
+ expect(controlsId).toBeTruthy()
177
+ expect(topLevelTable.find(`#${controlsId}`).length).toBe(1)
178
+ })
179
+
133
180
  it('renders a sub table for the map with correct values', () => {
134
181
  expect(subTable.length).toBe(1)
135
182
  })
@@ -13,10 +13,11 @@ var createDatasetCard = function createDatasetCard(dataset) {
13
13
  dataset: dataset
14
14
  });
15
15
  };
16
- var _default = exports.default = function _default(_ref) {
16
+ var DatasetList = function DatasetList(_ref) {
17
17
  var _ref$datasets = _ref.datasets,
18
18
  datasets = _ref$datasets === void 0 ? [] : _ref$datasets;
19
19
  return /*#__PURE__*/React.createElement("dataset-list", {
20
20
  "data-testid": "dataset-list"
21
21
  }, datasets.map(createDatasetCard) || /*#__PURE__*/React.createElement("div", null));
22
- };
22
+ };
23
+ var _default = exports.default = DatasetList;