@dhis2/analytics 26.2.0-alpha.1 → 26.2.0-cumulative-values-alpha.1

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 (51) hide show
  1. package/build/cjs/__demo__/PivotTable.stories.js +69 -29
  2. package/build/cjs/api/analytics/Analytics.js +0 -7
  3. package/build/cjs/api/analytics/AnalyticsBase.js +6 -24
  4. package/build/cjs/api/analytics/AnalyticsRequest.js +10 -33
  5. package/build/cjs/api/analytics/AnalyticsRequestBase.js +1 -3
  6. package/build/cjs/api/analytics/AnalyticsRequestPropertiesMixin.js +0 -19
  7. package/build/cjs/api/analytics/__tests__/AnalyticsTrackedEntities.spec.js +44 -0
  8. package/build/cjs/api/analytics/__tests__/__snapshots__/AnalyticsTrackedEntities.spec.js.snap +3 -0
  9. package/build/cjs/api/analytics/utils.js +2 -23
  10. package/build/cjs/components/Options/VisualizationOptions.js +1 -1
  11. package/build/cjs/components/Options/styles/VisualizationOptions.style.js +8 -1
  12. package/build/cjs/components/RichText/Editor.bk/Editor.js +40 -0
  13. package/build/cjs/components/RichText/Editor.bk/__tests__/Editor.spec.js +29 -0
  14. package/build/cjs/components/RichText/Editor.bk/__tests__/convertCtrlKey.spec.js +205 -0
  15. package/build/cjs/components/RichText/Editor.bk/convertCtrlKey.js +87 -0
  16. package/build/cjs/components/RichText/Parser.bk/MdParser.js +107 -0
  17. package/build/cjs/components/RichText/Parser.bk/Parser.js +34 -0
  18. package/build/cjs/components/RichText/Parser.bk/__tests__/MdParser.spec.js +34 -0
  19. package/build/cjs/components/RichText/Parser.bk/__tests__/Parser.spec.js +41 -0
  20. package/build/cjs/locales/uz_UZ_Cyrl/translations.json +2 -2
  21. package/build/cjs/modules/layout/dimension.js +2 -9
  22. package/build/cjs/modules/layout/dimensionCreate.js +0 -3
  23. package/build/cjs/modules/pivotTable/PivotTableEngine.js +119 -57
  24. package/build/cjs/visualizations/config/generators/dhis/singleValue.js.xp1 +478 -0
  25. package/build/es/__demo__/PivotTable.stories.js +69 -29
  26. package/build/es/api/analytics/Analytics.js +0 -7
  27. package/build/es/api/analytics/AnalyticsBase.js +6 -24
  28. package/build/es/api/analytics/AnalyticsRequest.js +10 -33
  29. package/build/es/api/analytics/AnalyticsRequestBase.js +1 -3
  30. package/build/es/api/analytics/AnalyticsRequestPropertiesMixin.js +0 -19
  31. package/build/es/api/analytics/__tests__/AnalyticsTrackedEntities.spec.js +41 -0
  32. package/build/es/api/analytics/__tests__/__snapshots__/AnalyticsTrackedEntities.spec.js.snap +3 -0
  33. package/build/es/api/analytics/utils.js +1 -20
  34. package/build/es/components/Options/VisualizationOptions.js +2 -2
  35. package/build/es/components/Options/styles/VisualizationOptions.style.js +6 -0
  36. package/build/es/components/RichText/Editor.bk/Editor.js +30 -0
  37. package/build/es/components/RichText/Editor.bk/__tests__/Editor.spec.js +26 -0
  38. package/build/es/components/RichText/Editor.bk/__tests__/convertCtrlKey.spec.js +202 -0
  39. package/build/es/components/RichText/Editor.bk/convertCtrlKey.js +80 -0
  40. package/build/es/components/RichText/Parser.bk/MdParser.js +99 -0
  41. package/build/es/components/RichText/Parser.bk/Parser.js +24 -0
  42. package/build/es/components/RichText/Parser.bk/__tests__/MdParser.spec.js +31 -0
  43. package/build/es/components/RichText/Parser.bk/__tests__/Parser.spec.js +38 -0
  44. package/build/es/locales/uz_UZ_Cyrl/translations.json +2 -2
  45. package/build/es/modules/layout/dimension.js +1 -7
  46. package/build/es/modules/layout/dimensionCreate.js +1 -4
  47. package/build/es/modules/pivotTable/PivotTableEngine.js +119 -57
  48. package/build/es/visualizations/config/generators/dhis/singleValue.js.xp1 +478 -0
  49. package/package.json +1 -1
  50. package/build/cjs/api/analytics/AnalyticsTrackedEntities.js +0 -31
  51. package/build/es/api/analytics/AnalyticsTrackedEntities.js +0 -24
@@ -6,15 +6,12 @@ import AnalyticsEnrollments from './AnalyticsEnrollments.js';
6
6
  import AnalyticsEvents from './AnalyticsEvents.js';
7
7
  import AnalyticsRequest from './AnalyticsRequest.js';
8
8
  import AnalyticsResponse from './AnalyticsResponse.js';
9
- import AnalyticsTrackedEntities from './AnalyticsTrackedEntities.js';
10
9
 
11
10
  /**
12
11
  * @description
13
12
  * Analytics class used to request analytics data from Web API.
14
13
  *
15
14
  * @requires analytics.AnalyticsAggregate
16
- * @requires analytics.AnalyticsTrackedEntities
17
- * @requires analytics.AnalyticsEnrollments
18
15
  * @requires analytics.AnalyticsEvents
19
16
  * @requires analytics.AnalyticsRequest
20
17
  * @requires analytics.AnalyticsResponse
@@ -34,7 +31,6 @@ import AnalyticsTrackedEntities from './AnalyticsTrackedEntities.js';
34
31
  class Analytics {
35
32
  /**
36
33
  * @param {!module:analytics.AnalyticsAggregate} analyticsAggregate The AnalyticsAggregate instance
37
- * @param {!module:analytics.AnalyticsTrackedEntities} analyticsTrackedEntities The AnalyticsTrackedEntities instance
38
34
  * @param {!module:analytics.AnalyticsEnrollments} analyticsEnrollments The AnalyticsEnrollments instance
39
35
  * @param {!module:analytics.AnalyticsEvents} analyticsEvents The AnalyticsEvents instance
40
36
  * @param {!module:analytics.AnalyticsRequest} analyticsRequest The AnalyticsRequest class
@@ -43,14 +39,12 @@ class Analytics {
43
39
  constructor(_ref) {
44
40
  let {
45
41
  aggregate,
46
- trackedEntities,
47
42
  enrollments,
48
43
  events,
49
44
  request,
50
45
  response
51
46
  } = _ref;
52
47
  this.aggregate = aggregate;
53
- this.trackedEntities = trackedEntities;
54
48
  this.enrollments = enrollments;
55
49
  this.events = events;
56
50
  this.request = request;
@@ -73,7 +67,6 @@ class Analytics {
73
67
  if (!Analytics.getAnalytics.analytics) {
74
68
  Analytics.getAnalytics.analytics = new Analytics({
75
69
  aggregate: new AnalyticsAggregate(dataEngine),
76
- trackedEntities: new AnalyticsTrackedEntities(dataEngine),
77
70
  enrollments: new AnalyticsEnrollments(dataEngine),
78
71
  events: new AnalyticsEvents(dataEngine),
79
72
  request: AnalyticsRequest,
@@ -1,19 +1,13 @@
1
1
  import sortBy from 'lodash/sortBy';
2
2
  import AnalyticsRequest from './AnalyticsRequest.js';
3
- import { formatRequestPath } from './utils.js';
4
3
  const analyticsQuery = {
5
4
  resource: 'analytics',
6
5
  id: _ref => {
7
6
  let {
8
7
  path,
9
- program,
10
- trackedEntityType
8
+ program
11
9
  } = _ref;
12
- return formatRequestPath({
13
- path,
14
- program,
15
- trackedEntityType
16
- });
10
+ return [path, program].filter(Boolean).join('/');
17
11
  },
18
12
  params: _ref2 => {
19
13
  let {
@@ -33,14 +27,9 @@ const analyticsDataQuery = {
33
27
  id: _ref3 => {
34
28
  let {
35
29
  path,
36
- program,
37
- trackedEntityType
30
+ program
38
31
  } = _ref3;
39
- return formatRequestPath({
40
- path,
41
- program,
42
- trackedEntityType
43
- });
32
+ return [path, program].filter(Boolean).join('/');
44
33
  },
45
34
  params: _ref4 => {
46
35
  let {
@@ -62,14 +51,9 @@ const analyticsMetaDataQuery = {
62
51
  id: _ref5 => {
63
52
  let {
64
53
  path,
65
- program,
66
- trackedEntityType
54
+ program
67
55
  } = _ref5;
68
- return formatRequestPath({
69
- path,
70
- program,
71
- trackedEntityType
72
- });
56
+ return [path, program].filter(Boolean).join('/');
73
57
  },
74
58
  params: _ref6 => {
75
59
  let {
@@ -161,7 +145,6 @@ class AnalyticsBase {
161
145
  variables: {
162
146
  path: req.path,
163
147
  program: req.program,
164
- trackedEntityType: req.trackedEntityType,
165
148
  dimensions: generateDimensionStrings(req.dimensions),
166
149
  filters: generateDimensionStrings(req.filters),
167
150
  parameters: req.parameters,
@@ -204,7 +187,6 @@ class AnalyticsBase {
204
187
  variables: {
205
188
  path: req.path,
206
189
  program: req.program,
207
- trackedEntityType: req.trackedEntityType,
208
190
  dimensions: generateDimensionStrings(req.dimensions, options),
209
191
  filters: generateDimensionStrings(req.filters, options),
210
192
  parameters: req.parameters
@@ -4,7 +4,6 @@ import AnalyticsRequestBase from './AnalyticsRequestBase.js';
4
4
  import AnalyticsRequestDimensionsMixin from './AnalyticsRequestDimensionsMixin.js';
5
5
  import AnalyticsRequestFiltersMixin from './AnalyticsRequestFiltersMixin.js';
6
6
  import AnalyticsRequestPropertiesMixin from './AnalyticsRequestPropertiesMixin.js';
7
- import { formatDimension } from './utils.js';
8
7
 
9
8
  /**
10
9
  * @description
@@ -42,7 +41,6 @@ class AnalyticsRequest extends AnalyticsRequestDimensionsMixin(AnalyticsRequestF
42
41
  fromVisualization(visualization) {
43
42
  let passFilterAsDimension = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : false;
44
43
  let request = this;
45
- const outputType = visualization.outputType;
46
44
 
47
45
  // extract dimensions from visualization
48
46
  const columns = visualization.columns || [];
@@ -53,28 +51,19 @@ class AnalyticsRequest extends AnalyticsRequestDimensionsMixin(AnalyticsRequestF
53
51
  if ((_d$legendSet = d.legendSet) !== null && _d$legendSet !== void 0 && _d$legendSet.id) {
54
52
  dimension += `-${d.legendSet.id}`;
55
53
  }
54
+ if ((_d$programStage = d.programStage) !== null && _d$programStage !== void 0 && _d$programStage.id) {
55
+ dimension = `${d.programStage.id}.${dimension}`;
56
+ }
56
57
  if (d.filter) {
57
58
  dimension += `:${d.filter}`;
58
59
  }
59
- const programStageId = (_d$programStage = d.programStage) === null || _d$programStage === void 0 ? void 0 : _d$programStage.id;
60
60
  if ((_d$repetition = d.repetition) !== null && _d$repetition !== void 0 && (_d$repetition$indexes = _d$repetition.indexes) !== null && _d$repetition$indexes !== void 0 && _d$repetition$indexes.length) {
61
61
  d.repetition.indexes.forEach(index => {
62
- var _d$program;
63
- request = request.addDimension(formatDimension({
64
- programId: (_d$program = d.program) === null || _d$program === void 0 ? void 0 : _d$program.id,
65
- programStageId: `${programStageId}[${index}]`,
66
- dimension,
67
- outputType
68
- }));
62
+ request = request.addDimension(dimension.replace(/\./, `[${index}].`));
69
63
  });
70
64
  } else {
71
- var _d$program2, _d$items;
72
- request = request.addDimension(formatDimension({
73
- programId: (_d$program2 = d.program) === null || _d$program2 === void 0 ? void 0 : _d$program2.id,
74
- programStageId,
75
- dimension,
76
- outputType
77
- }), (_d$items = d.items) === null || _d$items === void 0 ? void 0 : _d$items.map(item => item.id));
65
+ var _d$items;
66
+ request = request.addDimension(dimension, (_d$items = d.items) === null || _d$items === void 0 ? void 0 : _d$items.map(item => item.id));
78
67
  }
79
68
  });
80
69
 
@@ -89,29 +78,17 @@ class AnalyticsRequest extends AnalyticsRequestDimensionsMixin(AnalyticsRequestF
89
78
  request = request.addDimension(f.dimension, (_f$items = f.items) === null || _f$items === void 0 ? void 0 : _f$items.map(item => item.id));
90
79
  } else {
91
80
  var _f$programStage, _f$repetition, _f$repetition$indexes;
92
- let filterString = f.dimension;
81
+ let filterString = (_f$programStage = f.programStage) !== null && _f$programStage !== void 0 && _f$programStage.id ? `${f.programStage.id}.${f.dimension}` : f.dimension;
93
82
  if (f.filter) {
94
83
  filterString += `:${f.filter}`;
95
84
  }
96
- const programStageId = (_f$programStage = f.programStage) === null || _f$programStage === void 0 ? void 0 : _f$programStage.id;
97
85
  if ((_f$repetition = f.repetition) !== null && _f$repetition !== void 0 && (_f$repetition$indexes = _f$repetition.indexes) !== null && _f$repetition$indexes !== void 0 && _f$repetition$indexes.length) {
98
86
  f.repetition.indexes.forEach(index => {
99
- var _f$program;
100
- request = request.addFilter(formatDimension({
101
- programId: (_f$program = f.program) === null || _f$program === void 0 ? void 0 : _f$program.id,
102
- programStageId: `${programStageId}[${index}]`,
103
- dimension: filterString,
104
- outputType
105
- }));
87
+ request = request.addFilter(filterString.replace(/\./, `[${index}].`));
106
88
  });
107
89
  } else {
108
- var _f$program2, _f$items2;
109
- request = request.addFilter(formatDimension({
110
- programId: (_f$program2 = f.program) === null || _f$program2 === void 0 ? void 0 : _f$program2.id,
111
- programStageId,
112
- dimension: filterString,
113
- outputType
114
- }), (_f$items2 = f.items) === null || _f$items2 === void 0 ? void 0 : _f$items2.map(item => item.id));
90
+ var _f$items2;
91
+ request = request.addFilter(filterString, (_f$items2 = f.items) === null || _f$items2 === void 0 ? void 0 : _f$items2.map(item => item.id));
115
92
  }
116
93
  }
117
94
  });
@@ -18,7 +18,6 @@ class AnalyticsRequestBase {
18
18
  format = 'json',
19
19
  path,
20
20
  program,
21
- trackedEntityType,
22
21
  dimensions = [],
23
22
  filters = [],
24
23
  parameters = {}
@@ -27,7 +26,6 @@ class AnalyticsRequestBase {
27
26
  this.format = format.toLowerCase();
28
27
  this.path = path;
29
28
  this.program = program;
30
- this.trackedEntityType = trackedEntityType;
31
29
  this.dimensions = dimensions;
32
30
  this.filters = filters;
33
31
  this.parameters = {
@@ -66,7 +64,7 @@ class AnalyticsRequestBase {
66
64
  }
67
65
  return dimension;
68
66
  });
69
- const endPoint = [this.endPoint, this.path, this.program, this.trackedEntityType].filter(Boolean).join('/');
67
+ const endPoint = [this.endPoint, this.path, this.program].filter(e => !!e).join('/');
70
68
  return `${endPoint}.${this.format}?dimension=${encodedDimensions.join('&dimension=')}`;
71
69
  }
72
70
 
@@ -491,25 +491,6 @@ class extends base {
491
491
  return new AnalyticsRequest(this);
492
492
  }
493
493
 
494
- /**
495
- * Sets the tracked entity type for the request.
496
- * It appends the tracked entity type id to the request's path.
497
- *
498
- * @param {!String} trackedEntityType The tracked entity type id
499
- *
500
- * @returns {AnalyticsRequest} A new instance of the class for chaining purposes
501
- *
502
- * @example
503
- * const req = new analytics.request()
504
- * .withTrackedEntityType('nEenWmSyUEp');
505
- */
506
- withTrackedEntityType(trackedEntityType) {
507
- if (trackedEntityType) {
508
- this.trackedEntityType = trackedEntityType;
509
- }
510
- return new AnalyticsRequest(this);
511
- }
512
-
513
494
  /**
514
495
  * Sets the program for the request.
515
496
  * It appends the program id to the request's path.
@@ -0,0 +1,41 @@
1
+ import fixtures from '../../../__fixtures__/fixtures.js';
2
+ import DataEngineMock from '../__mocks__/DataEngine.js';
3
+ import AnalyticsRequest from '../AnalyticsRequest.js';
4
+ import AnalyticsTrackedEntities from '../AnalyticsTrackedEntities.js';
5
+ describe.skip('analytics.trackedEntity', () => {
6
+ let enrollments;
7
+ let request;
8
+ let dataEngineMock;
9
+ let fixture;
10
+ beforeEach(() => {
11
+ dataEngineMock = new DataEngineMock();
12
+ DataEngineMock.mockClear();
13
+ enrollments = new AnalyticsTrackedEntities();
14
+ });
15
+ it('should not be allowed to be called without new', () => {
16
+ expect(() => AnalyticsTrackedEntities()).toThrowErrorMatchingSnapshot();
17
+ });
18
+ it('should use the dataEngine object when it is passed', () => {
19
+ const dataEngineMockObject = {};
20
+ enrollments = new AnalyticsTrackedEntities(dataEngineMockObject);
21
+ expect(enrollments.dataEngine).toBe(dataEngineMockObject);
22
+ });
23
+ describe('.getQuery()', () => {
24
+ beforeEach(() => {
25
+ enrollments = new AnalyticsTrackedEntities(new DataEngineMock());
26
+ request = new AnalyticsRequest().addOrgUnitDimension('ImspTQPwCqd').addDimension('WZbXY0S00lP.de0FEHSIoxh').addDimension('WZbXY0S00lP.sWoqcoByYmD').addPeriodFilter('LAST_MONTH').withTrackedEntity('nEenWmSyUEp').withAsc('ENROLLMENTDATE').withOuMode('DESCENDANTS').withColumns('w75KJ2mc4zz').withPage(1).withPageSize(10);
27
+ fixture = fixtures.get('/api/analytics/enrollments'); // XXX
28
+
29
+ dataEngineMock.query.mockReturnValue(Promise.resolve({
30
+ data: fixture
31
+ }));
32
+ });
33
+ it('should be a function', () => {
34
+ expect(enrollments.getQuery).toBeInstanceOf(Function);
35
+ });
36
+ it('should resolve a promise with data', () => enrollments.getQuery(request).then(data => {
37
+ expect(data.width).toEqual(fixture.width);
38
+ expect(data.height).toEqual(fixture.height);
39
+ }));
40
+ });
41
+ });
@@ -0,0 +1,3 @@
1
+ // Jest Snapshot v1, https://goo.gl/fbAQLP
2
+
3
+ exports[`analytics.trackedEntity should not be allowed to be called without new 1`] = `"Class constructor AnalyticsTrackedEntities cannot be invoked without 'new'"`;
@@ -2,23 +2,4 @@
2
2
  const whitelistURI = ',&$=/;:';
3
3
  const whitelistURICodes = whitelistURI.split('').map(c => encodeURIComponent(c));
4
4
  const whitelistRegExp = new RegExp(`(?:${whitelistURICodes.join('|')})`, 'g');
5
- export const customEncodeURIComponent = uri => encodeURIComponent(uri).replace(whitelistRegExp, decodeURIComponent);
6
- export const formatRequestPath = _ref => {
7
- let {
8
- path,
9
- program,
10
- trackedEntityType
11
- } = _ref;
12
- return [path, program, trackedEntityType].filter(Boolean).join('/');
13
- };
14
- export const formatDimension = _ref2 => {
15
- let {
16
- outputType,
17
- programId,
18
- programStageId,
19
- dimension
20
- } = _ref2;
21
- return [
22
- // XXX it would be clearer to have this consistent with what is sent in the request as for EVENT/ENROLLMENT
23
- outputType === 'TRACKED_ENTITY' ? programId : undefined, programStageId, dimension].filter(Boolean).join('.');
24
- };
5
+ export const customEncodeURIComponent = uri => encodeURIComponent(uri).replace(whitelistRegExp, decodeURIComponent);
@@ -2,7 +2,7 @@ import i18n from '@dhis2/d2-i18n';
2
2
  import { ButtonStrip, Modal, ModalTitle, ModalContent, ModalActions, Button, FieldSet, Legend, TabBar, Tab, Help } from '@dhis2/ui';
3
3
  import PropTypes from 'prop-types';
4
4
  import React, { useState } from 'react';
5
- import { modalContent, tabSection, tabSectionTitle, tabSectionTitleMargin, tabSectionOption, tabSectionOptionItem, tabSectionOptionToggleable, tabSectionToggleableSubsection, tabSectionOptionComplexInline, tabSectionOptionText, tabBar, tabContent, tabSectionOptionIcon } from './styles/VisualizationOptions.style.js';
5
+ import { modalContent, tabSection, tabSectionTitle, tabSectionTitleDisabled, tabSectionTitleMargin, tabSectionOption, tabSectionOptionItem, tabSectionOptionToggleable, tabSectionToggleableSubsection, tabSectionOptionComplexInline, tabSectionOptionText, tabBar, tabContent, tabSectionOptionIcon } from './styles/VisualizationOptions.style.js';
6
6
  const VisualizationOptions = _ref => {
7
7
  let {
8
8
  initiallyActiveTabKey,
@@ -59,7 +59,7 @@ const VisualizationOptions = _ref => {
59
59
  }, label);
60
60
  })), tabBar.styles), /*#__PURE__*/React.createElement("div", {
61
61
  className: tabContent.className
62
- }, tabs[activeTabIndex].content, tabContent.styles, tabSection.styles, tabSectionTitle.styles, tabSectionTitleMargin.styles, tabSectionOption.styles, tabSectionOptionItem.styles, tabSectionOptionToggleable.styles, tabSectionToggleableSubsection.styles, tabSectionOptionComplexInline.styles, tabSectionOptionText.styles, tabSectionOptionIcon.styles));
62
+ }, tabs[activeTabIndex].content, tabContent.styles, tabSection.styles, tabSectionTitle.styles, tabSectionTitleDisabled.styles, tabSectionTitleMargin.styles, tabSectionOption.styles, tabSectionOptionItem.styles, tabSectionOptionToggleable.styles, tabSectionToggleableSubsection.styles, tabSectionOptionComplexInline.styles, tabSectionOptionText.styles, tabSectionOptionIcon.styles));
63
63
  };
64
64
  return /*#__PURE__*/React.createElement(Modal, {
65
65
  onClose: onClose,
@@ -38,6 +38,12 @@ export const tabSectionTitle = {
38
38
  }, [`span.jsx-3115295887{display:inline-block;padding-bottom:${spacers.dp12};font-size:15px;color:${colors.grey900};font-weight:500;-webkit-letter-spacing:0.2px;-moz-letter-spacing:0.2px;-ms-letter-spacing:0.2px;letter-spacing:0.2px;}`]),
39
39
  className: "jsx-3115295887"
40
40
  };
41
+ export const tabSectionTitleDisabled = {
42
+ styles: /*#__PURE__*/React.createElement(_JSXStyle, {
43
+ id: "3352433486"
44
+ }, [`span.jsx-3352433486{color:${colors.grey600};}`]),
45
+ className: "jsx-3352433486"
46
+ };
41
47
  export const tabSectionTitleMargin = {
42
48
  styles: /*#__PURE__*/React.createElement(_JSXStyle, {
43
49
  id: "642558349"
@@ -0,0 +1,30 @@
1
+ function _defineProperty(obj, key, value) { key = _toPropertyKey(key); if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }
2
+ function _toPropertyKey(arg) { var key = _toPrimitive(arg, "string"); return typeof key === "symbol" ? key : String(key); }
3
+ function _toPrimitive(input, hint) { if (typeof input !== "object" || input === null) return input; var prim = input[Symbol.toPrimitive]; if (prim !== undefined) { var res = prim.call(input, hint || "default"); if (typeof res !== "object") return res; throw new TypeError("@@toPrimitive must return a primitive value."); } return (hint === "string" ? String : Number)(input); }
4
+ import PropTypes from 'prop-types';
5
+ import React, { Component } from 'react';
6
+ import convertCtrlKey from './convertCtrlKey.js';
7
+ class Editor extends Component {
8
+ constructor() {
9
+ super(...arguments);
10
+ _defineProperty(this, "onKeyDown", event => {
11
+ convertCtrlKey(event, this.props.onEdit);
12
+ });
13
+ }
14
+ render() {
15
+ const {
16
+ children
17
+ } = this.props;
18
+ return /*#__PURE__*/React.createElement("div", {
19
+ onKeyDown: this.onKeyDown
20
+ }, children);
21
+ }
22
+ }
23
+ Editor.defaultProps = {
24
+ onEdit: null
25
+ };
26
+ Editor.propTypes = {
27
+ children: PropTypes.oneOfType([PropTypes.arrayOf(PropTypes.node), PropTypes.node]),
28
+ onEdit: PropTypes.func
29
+ };
30
+ export default Editor;
@@ -0,0 +1,26 @@
1
+ import { shallow } from 'enzyme';
2
+ import React from 'react';
3
+ import convertCtrlKey from '../convertCtrlKey.js';
4
+ import Editor from '../Editor.js';
5
+ jest.mock('../convertCtrlKey');
6
+ describe('RichText: Editor component', () => {
7
+ let richTextEditor;
8
+ const componentProps = {
9
+ onEdit: jest.fn()
10
+ };
11
+ beforeEach(() => {
12
+ convertCtrlKey.mockClear();
13
+ });
14
+ const renderComponent = props => {
15
+ return shallow( /*#__PURE__*/React.createElement(Editor, props, /*#__PURE__*/React.createElement("input", null)));
16
+ };
17
+ it('renders a result', () => {
18
+ richTextEditor = renderComponent(componentProps);
19
+ expect(richTextEditor).toHaveLength(1);
20
+ });
21
+ it('calls convertCtrlKey on keydown', () => {
22
+ richTextEditor = renderComponent(componentProps);
23
+ richTextEditor.simulate('keyDown');
24
+ expect(convertCtrlKey).toHaveBeenCalled();
25
+ });
26
+ });
@@ -0,0 +1,202 @@
1
+ import convertCtrlKey from '../convertCtrlKey.js';
2
+ describe('convertCtrlKey', () => {
3
+ it('does not trigger callback if no ctrl key', () => {
4
+ const cb = jest.fn();
5
+ const e = {
6
+ key: 'j',
7
+ preventDefault: () => {}
8
+ };
9
+ convertCtrlKey(e, cb);
10
+ expect(cb).not.toHaveBeenCalled();
11
+ });
12
+ describe('when ctrl key + "b" pressed', () => {
13
+ it('triggers callback with open/close markers and caret pos in between', () => {
14
+ const cb = jest.fn();
15
+ const e = {
16
+ key: 'b',
17
+ ctrlKey: true,
18
+ target: {
19
+ selectionStart: 0,
20
+ selectionEnd: 0,
21
+ value: 'rainbow dash'
22
+ },
23
+ preventDefault: () => {}
24
+ };
25
+ convertCtrlKey(e, cb);
26
+ expect(cb).toHaveBeenCalled();
27
+ expect(cb).toHaveBeenCalledWith('** rainbow dash', 1);
28
+ });
29
+ it('triggers callback with open/close markers and caret pos in between (end of text)', () => {
30
+ const cb = jest.fn();
31
+ const e = {
32
+ key: 'b',
33
+ ctrlKey: true,
34
+ target: {
35
+ selectionStart: 22,
36
+ selectionEnd: 22,
37
+ value: 'rainbow dash is purple'
38
+ },
39
+ preventDefault: () => {}
40
+ };
41
+ convertCtrlKey(e, cb);
42
+ expect(cb).toHaveBeenCalled();
43
+ expect(cb).toHaveBeenCalledWith('rainbow dash is purple **', 24);
44
+ });
45
+ it('triggers callback with open/close markers mid-text with surrounding spaces (1)', () => {
46
+ const cb = jest.fn();
47
+ const e = {
48
+ key: 'b',
49
+ metaKey: true,
50
+ target: {
51
+ selectionStart: 4,
52
+ // caret located just before "quick"
53
+ selectionEnd: 4,
54
+ value: 'the quick brown fox'
55
+ },
56
+ preventDefault: () => {}
57
+ };
58
+ convertCtrlKey(e, cb);
59
+ expect(cb).toHaveBeenCalled();
60
+ expect(cb).toHaveBeenCalledWith('the ** quick brown fox', 5);
61
+ });
62
+ it('triggers callback with open/close markers mid-text with surrounding spaces (2)', () => {
63
+ const cb = jest.fn();
64
+ const e = {
65
+ key: 'b',
66
+ metaKey: true,
67
+ target: {
68
+ selectionStart: 3,
69
+ // caret located just after "the"
70
+ selectionEnd: 3,
71
+ value: 'the quick brown fox'
72
+ },
73
+ preventDefault: () => {}
74
+ };
75
+ convertCtrlKey(e, cb);
76
+ expect(cb).toHaveBeenCalled();
77
+ expect(cb).toHaveBeenCalledWith('the ** quick brown fox', 5);
78
+ });
79
+ it('triggers callback with correct double markers and padding', () => {
80
+ const cb = jest.fn();
81
+ const e = {
82
+ key: 'b',
83
+ metaKey: true,
84
+ target: {
85
+ selectionStart: 9,
86
+ // between the underscores
87
+ selectionEnd: 9,
88
+ value: 'rainbow __'
89
+ },
90
+ preventDefault: () => {}
91
+ };
92
+ convertCtrlKey(e, cb);
93
+ expect(cb).toHaveBeenCalled();
94
+ expect(cb).toHaveBeenCalledWith('rainbow _**_', 10);
95
+ });
96
+ describe('selected text', () => {
97
+ it('triggers callback with open/close markers around text and caret pos after closing marker', () => {
98
+ const cb = jest.fn();
99
+ const e = {
100
+ key: 'b',
101
+ metaKey: true,
102
+ target: {
103
+ selectionStart: 5,
104
+ // "ow da" is selected
105
+ selectionEnd: 10,
106
+ value: 'rainbow dash is purple'
107
+ },
108
+ preventDefault: () => {}
109
+ };
110
+ convertCtrlKey(e, cb);
111
+ expect(cb).toHaveBeenCalled();
112
+ expect(cb).toHaveBeenCalledWith('rainb *ow da* sh is purple', 13);
113
+ });
114
+ it('triggers callback with open/close markers around text when starting at beginning of line', () => {
115
+ const cb = jest.fn();
116
+ const e = {
117
+ key: 'b',
118
+ metaKey: true,
119
+ target: {
120
+ selectionStart: 0,
121
+ // "rainbow" is selected
122
+ selectionEnd: 7,
123
+ value: 'rainbow dash is purple'
124
+ },
125
+ preventDefault: () => {}
126
+ };
127
+ convertCtrlKey(e, cb);
128
+ expect(cb).toHaveBeenCalled();
129
+ expect(cb).toHaveBeenCalledWith('*rainbow* dash is purple', 9);
130
+ });
131
+ it('triggers callback with open/close markers around text when ending at end of line', () => {
132
+ const cb = jest.fn();
133
+ const e = {
134
+ key: 'b',
135
+ metaKey: true,
136
+ target: {
137
+ selectionStart: 16,
138
+ // "purple" is selected
139
+ selectionEnd: 22,
140
+ value: 'rainbow dash is purple'
141
+ },
142
+ preventDefault: () => {}
143
+ };
144
+ convertCtrlKey(e, cb);
145
+ expect(cb).toHaveBeenCalled();
146
+ expect(cb).toHaveBeenCalledWith('rainbow dash is *purple*', 24);
147
+ });
148
+ it('triggers callback with open/close markers around word', () => {
149
+ const cb = jest.fn();
150
+ const e = {
151
+ key: 'b',
152
+ metaKey: true,
153
+ target: {
154
+ selectionStart: 8,
155
+ // "dash" is selected
156
+ selectionEnd: 12,
157
+ value: 'rainbow dash is purple'
158
+ },
159
+ preventDefault: () => {}
160
+ };
161
+ convertCtrlKey(e, cb);
162
+ expect(cb).toHaveBeenCalled();
163
+ expect(cb).toHaveBeenCalledWith('rainbow *dash* is purple', 14);
164
+ });
165
+ it('triggers callback with leading/trailing spaces trimmed from selection', () => {
166
+ const cb = jest.fn();
167
+ const e = {
168
+ key: 'b',
169
+ metaKey: true,
170
+ target: {
171
+ selectionStart: 8,
172
+ // " dash " is selected (note leading and trailing space)
173
+ selectionEnd: 13,
174
+ value: 'rainbow dash is purple'
175
+ },
176
+ preventDefault: () => {}
177
+ };
178
+ convertCtrlKey(e, cb);
179
+ expect(cb).toHaveBeenCalled();
180
+ expect(cb).toHaveBeenCalledWith('rainbow *dash* is purple', 14);
181
+ });
182
+ });
183
+ });
184
+ describe('when ctrl key + "i" pressed', () => {
185
+ it('triggers callback with open/close italics markers and caret pos in between', () => {
186
+ const cb = jest.fn();
187
+ const e = {
188
+ key: 'i',
189
+ ctrlKey: true,
190
+ target: {
191
+ selectionStart: 0,
192
+ selectionEnd: 0,
193
+ value: ''
194
+ },
195
+ preventDefault: () => {}
196
+ };
197
+ convertCtrlKey(e, cb);
198
+ expect(cb).toHaveBeenCalled();
199
+ expect(cb).toHaveBeenCalledWith('__', 1);
200
+ });
201
+ });
202
+ });