@capillarytech/creatives-library 7.17.21-alpha.0 → 7.17.21-alpha.2

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.
@@ -5,6 +5,7 @@ export const GET_SIDEBAR_FAILURE = 'app/App/GET_SIDEBAR_FAILURE';
5
5
  export const STORE2DOOR_PLUS_ENABLED = 'STORE2DOOR_PLUS_ENABLED';
6
6
  export const TRAI_DLT = 'TRAI_DLT';
7
7
  export const CARD_BASED_SCOPE = 'CARD_BASED_SCOPE';
8
+ export const HOSPITALITY_BASED_SCOPE = 'HOSPITALITY_BASED_SCOPE';
8
9
 
9
10
  export const CARD_RELATED_TAGS = [
10
11
  'card_series',
@@ -14,3 +15,25 @@ export const CARD_RELATED_TAGS = [
14
15
  'card_series_name',
15
16
  'card_name',
16
17
  ];
18
+
19
+ export const HOSPITALITY_RELATED_TAGS = [
20
+ "hospitality_series",
21
+ "tax_Code",
22
+ "roomTypeCode",
23
+ "bookingChannel",
24
+ "hotelReservationID_ResID_Type",
25
+ "lastModifyDateTime",
26
+ "guestCounts_IsPerRoom",
27
+ "ratePlan_RatePlanCode",
28
+ "resID_Value",
29
+ "numberOfUnits",
30
+ "rateTimeUnit",
31
+ "resStatus",
32
+ "timeSpan_End",
33
+ "roomStay_MarketCode",
34
+ "createDateTime",
35
+ "amount",
36
+ "timeSpan_Start",
37
+ "email",
38
+ "resID_Source",
39
+ ];
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@capillarytech/creatives-library",
3
3
  "author": "meharaj",
4
- "version": "7.17.21-alpha.0",
4
+ "version": "7.17.21-alpha.2",
5
5
  "description": "Capillary creatives ui",
6
6
  "main": "./index.js",
7
7
  "module": "./index.es.js",
package/utils/common.js CHANGED
@@ -5,6 +5,8 @@ import {
5
5
  TRAI_DLT,
6
6
  CARD_RELATED_TAGS,
7
7
  CARD_BASED_SCOPE,
8
+ HOSPITALITY_RELATED_TAGS,
9
+ HOSPITALITY_BASED_SCOPE,
8
10
  } from '../containers/App/constants';
9
11
  import { apiMessageFormatHandler } from './commonUtils';
10
12
 
@@ -54,16 +56,28 @@ export const hasCardBasedScope = Auth.hasFeatureAccess.bind(
54
56
  CARD_BASED_SCOPE,
55
57
  );
56
58
 
59
+ export const hasHospitalityBasedScope = Auth.hasFeatureAccess.bind(
60
+ null,
61
+ HOSPITALITY_BASED_SCOPE,
62
+ );
63
+
64
+ //filtering tags based on scope
65
+ export const filterTags = (tagsToFilter, tagsList) => tagsList?.filter(
66
+ (tag) => !tagsToFilter?.includes(tag?.definition?.value)
67
+ );
68
+
57
69
  export function getTreeStructuredTags({tagsList, userLocale = 'en', offerDetails = [], disableTagsDetails = {}}) {
58
- console.log('populating tags', (tagsList || []).length);
59
70
  const mainTags = {};
60
71
  const { disableRelatedTags, childTagsToDisable, parentTagstoDisable} = disableTagsDetails;
61
72
 
62
73
  let clonedTags = tagsList;
63
74
  if (!hasCardBasedScope()) {
64
- clonedTags = _.filter(tagsList, (tag) =>
65
- !CARD_RELATED_TAGS.includes(tag?.definition?.value)
66
- );
75
+ //filtering CARD_RELATED_TAGS if org does not have CARD_BASED_SCOPE feature enabled
76
+ clonedTags = filterTags(CARD_RELATED_TAGS, tagsList);
77
+ }
78
+ if (!hasHospitalityBasedScope()) {
79
+ //filtering HOSPITALITY_RELATED_TAGS if org does not have HOSPITALITY_BASED_SCOPE feature enabled
80
+ clonedTags = filterTags(HOSPITALITY_RELATED_TAGS, tagsList);
67
81
  }
68
82
  _.forEach(clonedTags, (temp) => {
69
83
  const tag = temp.definition;
@@ -0,0 +1,125 @@
1
+ export const tagsList = [
2
+ {
3
+ _id: "6501b8b2ddd26e9da234b7c0",
4
+ type: "TAG",
5
+ definition: {
6
+ "resolutionDetails": {
7
+ resolver: "EI",
8
+ reference:
9
+ "select check_in_date.date as timeSpan_Start from read_api_2000014.behavioural_catalog_reservation a join read_api_2000014.date check_in_date on check_in_date.date_id=a.dim_timespan_start_id join read_api_2000014.date modified_date on modified_date.date_id=a.dim_latest_updated_date_id join read_api_2000014.date lastmodifydate on lastmodifydate.date_id=a.dim_lastmodifydatetime_id join read_api_2000014.date d ON a.dim_event_date_id = d.date_id join read_api_2000014.date booking_date ON a.dim_createdatetime_id = booking_date.date_id join read_api_2000014.date check_out_date ON a.dim_timespan_end_id = check_out_date.date_id join (select resid_value, max(date) as max_updated_date from read_api_2000014.behavioural_catalog_reservation a join read_api_2000014.date d on d.date_id=a.dim_latest_updated_date_id group by 1 ) b on a.resid_value=b.resid_value and b.max_updated_date=modified_date.date where resstatus='Reserved' and datediff(check_in_date.date, date(now()) )= 3",
10
+ type: "QUERY",
11
+ },
12
+ "subType": "EXTERNAL",
13
+ "label": {
14
+ "zh-cn": "",
15
+ "en": "Checkin date",
16
+ },
17
+ "value": "timeSpan_Start",
18
+ "subtags": [],
19
+ "tag-header": false,
20
+ "supportedModules": [
21
+ {
22
+ mandatory: false,
23
+ layout: "sms",
24
+ context: "default",
25
+ },
26
+ {
27
+ mandatory: false,
28
+ layout: "sms",
29
+ context: "outbound",
30
+ },
31
+ {
32
+ mandatory: false,
33
+ layout: "email",
34
+ context: "default",
35
+ },
36
+ {
37
+ mandatory: false,
38
+ layout: "email",
39
+ context: "outbound",
40
+ },
41
+ ],
42
+ },
43
+ scope: {
44
+ tag: "STANDARD",
45
+ orgId: -1,
46
+ verticals: [],
47
+ },
48
+ isActive: true,
49
+ createdBy: 50685535,
50
+ updatedBy: 50685535,
51
+ createdAt: "2023-09-13T13:27:14.653Z",
52
+ updatedAt: "2023-09-13T13:27:14.653Z",
53
+ },
54
+ {
55
+ _id: "5a291cdd85c3db29a2041e5a",
56
+ type: "TAG",
57
+ definition: {
58
+ "label": {
59
+ "en": "Optout",
60
+ "ja-JP": "撤回",
61
+ },
62
+ "value": "optout",
63
+ "subtags": [],
64
+ "tag-header": false,
65
+ "supportedModules": [
66
+ {
67
+ context: "journey",
68
+ layout: "sms",
69
+ mandatory: false,
70
+ },
71
+ {
72
+ context: "outbound",
73
+ layout: "sms",
74
+ mandatory: false,
75
+ },
76
+ {
77
+ context: "default",
78
+ layout: "sms",
79
+ mandatory: false,
80
+ },
81
+ {
82
+ context: "coupon_expiry",
83
+ layout: "sms",
84
+ mandatory: false,
85
+ },
86
+ {
87
+ context: "coupons",
88
+ layout: "sms",
89
+ mandatory: false,
90
+ },
91
+ ],
92
+ },
93
+ scope: {
94
+ tag: "STANDARD",
95
+ orgId: -1,
96
+ verticals: [],
97
+ },
98
+ isActive: true,
99
+ createdBy: 4,
100
+ updatedBy: 15000449,
101
+ createdAt: "2017-12-07T10:50:05.800Z",
102
+ updatedAt: "2018-01-22T11:43:05.611Z",
103
+ },
104
+ ];
105
+ export const tagsToFilter = ["timeSpan_Start"];
106
+
107
+ export const output1 = [
108
+ {
109
+ disabled: false,
110
+ title: "Optout",
111
+ value: "{{optout}}",
112
+ },
113
+ ];
114
+ export const output2 = [
115
+ {
116
+ disabled: false,
117
+ title: "Checkin date",
118
+ value: "{{timeSpan_Start}}",
119
+ },
120
+ {
121
+ disabled: false,
122
+ title: "Optout",
123
+ value: "{{optout}}",
124
+ },
125
+ ];
@@ -0,0 +1,98 @@
1
+ import React from 'react';
2
+ import { FormattedMessage } from 'react-intl';
3
+ import "@testing-library/jest-dom";
4
+ import { filterTags, getTreeStructuredTags, handleInjectedData } from "../common";
5
+ import * as mockdata from "./common.mockdata";
6
+
7
+ describe("getTreeStructuredTags test", () => {
8
+ it("test for getTreeStructuredTags when tagsList is not empty", () => {
9
+ expect(getTreeStructuredTags({tagsList: mockdata.tagsList})).toEqual(mockdata.output1);
10
+ });
11
+ it("test for getTreeStructuredTags when tagsList is empty", () => {
12
+ expect(getTreeStructuredTags({tagsList: []})).toEqual([]);
13
+ });
14
+ it("test for filterTags", () => {
15
+ expect(filterTags(mockdata.tagsToFilter, mockdata.tagsList)).toEqual([mockdata.tagsList[1]]);
16
+ });
17
+ it("test for filterTags when tagsToFilter and tagsList is empty", () => {
18
+ expect(filterTags([], [])).toEqual([]);
19
+ });
20
+ });
21
+
22
+ describe("handleInjectedData", () => {
23
+ it("replaces name with intl key for top level tag", () => {
24
+ const data = {
25
+ tag1: {
26
+ name: "Registration Fields",
27
+ },
28
+ };
29
+
30
+ const result = handleInjectedData(data, "scope");
31
+
32
+ expect(result.tag1.name).toEqual(<FormattedMessage defaultMessage="Registration Fields" id="scope.RegistrationFields" values={{}} />);
33
+ });
34
+
35
+ it("adds tagType for Registration custom fields", () => {
36
+ const data = {
37
+ tag1: {
38
+ name: "Registration custom fields",
39
+ },
40
+ };
41
+
42
+ const result = handleInjectedData(data, "scope");
43
+
44
+ expect(result.tag1.name).toEqual(
45
+ <FormattedMessage defaultMessage="Registration custom fields" id="scope.Registrationcustomfields_name.CustomTagMessage" values={{}} />
46
+ );
47
+ });
48
+
49
+ it("adds tagType for Customer extended fields fields", () => {
50
+ const data = {
51
+ tag1: {
52
+ name: "Customer extended fields",
53
+ },
54
+ };
55
+
56
+ const result = handleInjectedData(data, "scope");
57
+
58
+ expect(result.tag1.name).toEqual(
59
+ <FormattedMessage defaultMessage="Customer extended fields" id="scope.Customerextendedfields_name.ExtendedTagMessage" values={{}} />
60
+ );
61
+ });
62
+
63
+
64
+ it("replaces subtag name with intl key", () => {
65
+ const data = {
66
+ tag1: {
67
+ subtags: {
68
+ subtag1: {
69
+ name: "First Name",
70
+ },
71
+ },
72
+ },
73
+ };
74
+
75
+ const result = handleInjectedData(data, "scope");
76
+
77
+ expect(result.tag1.subtags.subtag1.name).toEqual(<FormattedMessage defaultMessage="First Name" id="scope.FirstName" values={{}} />);
78
+ });
79
+
80
+ it("replaces subtag desc with intl key", () => {
81
+ const data = {
82
+ tag1: {
83
+ subtags: {
84
+ subtag1: {
85
+ desc: "Enter your first name",
86
+ },
87
+ },
88
+ },
89
+ };
90
+
91
+ const result = handleInjectedData(data, "scope");
92
+
93
+ expect(result.tag1.subtags.subtag1.desc).toEqual(
94
+ <FormattedMessage defaultMessage="Enter your first name" id="scope.Enteryourfirstname" values={{}} />
95
+ );
96
+ });
97
+ });
98
+
@@ -0,0 +1,21 @@
1
+ import "@testing-library/jest-dom";
2
+ import { getTreeStructuredTags } from "../common";
3
+ import * as mockdata from "./common.mockdata";
4
+
5
+ jest.mock('@capillarytech/cap-ui-utils', () => ({
6
+ Auth: {
7
+ hasAccess: () => jest.fn(() => true),
8
+ hasFeatureAccess: () => jest.fn(() => true),
9
+ authHoc: jest.fn(),
10
+ initialize: jest.fn(),
11
+ },
12
+ }));
13
+
14
+ describe("common utils test", () => {
15
+ it("test for getTreeStructuredTags when tagsList is not empty", () => {
16
+ expect(getTreeStructuredTags({tagsList: mockdata.tagsList})).toEqual(mockdata.output2);
17
+ });
18
+ it("test for getTreeStructuredTags when tagsList is empty", () => {
19
+ expect(getTreeStructuredTags({tagsList: []})).toEqual([]);
20
+ });
21
+ });
@@ -7,6 +7,8 @@
7
7
  import PropTypes from 'prop-types';
8
8
  import React from 'react';
9
9
  import { connect } from 'react-redux';
10
+ import moment from 'moment';
11
+ import { FONT_COLOR_05 } from '@capillarytech/cap-ui-library/styled/variables';
10
12
  // import styled from 'styled-components';
11
13
  import { CapSpin, CapModal, CapButton, CapPopover, CapInput, CapTree, CapSelect, CapTooltip, CapLink, CapIcon } from '@capillarytech/cap-ui-library';
12
14
 
@@ -15,10 +17,9 @@ import { FormattedMessage, injectIntl, intlShape } from 'react-intl';
15
17
  import { createStructuredSelector } from 'reselect';
16
18
  import messages from './messages';
17
19
  import { makeSelectLoyaltyPromotionDisplay } from '../../v2Containers/Cap/selectors';
18
- import { CARD_RELATED_TAGS } from '../../containers/App/constants';
19
- import { hasCardBasedScope } from '../../utils/common';
20
- import moment from 'moment';
21
- import { FONT_COLOR_05 } from '@capillarytech/cap-ui-library/styled/variables';
20
+ import { CARD_RELATED_TAGS, HOSPITALITY_RELATED_TAGS } from '../../containers/App/constants';
21
+ import { hasCardBasedScope, hasHospitalityBasedScope } from '../../utils/common';
22
+
22
23
  import {
23
24
  GET_TRANSLATION_MAPPED,
24
25
  JAPANESE_HELP_TEXT,
@@ -93,12 +94,12 @@ class CapTagList extends React.Component { // eslint-disable-line react/prefer-s
93
94
  let list = [];
94
95
  _.forEach(tags, (val = {}, key) => {
95
96
  if (_.has(val, 'subtags')) {
96
- if ((val.name || '').toLowerCase().indexOf(value.toLowerCase()) !== -1) {
97
+ if (val.name && typeof val.name === 'string' && (val.name || '').toLowerCase().indexOf(value.toLowerCase()) !== -1) {
97
98
  list.push(key);
98
99
  }
99
100
  const temp = this.getSearchedExpandedKeys(val.subtags, value);
100
101
  list = list.concat(temp);
101
- } else if ((val.name || '').toLowerCase().indexOf(value.toLowerCase()) !== -1) {
102
+ } else if (val.name && typeof val.name === 'string' && (val.name || '').toLowerCase().indexOf(value.toLowerCase()) !== -1) {
102
103
  list.push(key);
103
104
  }
104
105
  });
@@ -181,6 +182,10 @@ class CapTagList extends React.Component { // eslint-disable-line react/prefer-s
181
182
  if (!hasCardBasedScope()) {
182
183
  clonedTags = _.omit(clonedTags, CARD_RELATED_TAGS);
183
184
  }
185
+ if (!hasHospitalityBasedScope()) {
186
+ //filtering HOSPITALITY_RELATED_TAGS if org does not have HOSPITALITY_BASED_SCOPE feature enabled
187
+ clonedTags = _.omit(clonedTags, HOSPITALITY_RELATED_TAGS);
188
+ }
184
189
  _.forEach(clonedTags, (val = '', key) => {
185
190
  let supportedTagsString = '';
186
191
  _.forEach(val.supportedTags, (supportedTag) => {
@@ -0,0 +1,20 @@
1
+ /*
2
+ *
3
+ * ChannelTemplates actions
4
+ *
5
+ */
6
+
7
+ import * as types from './constants';
8
+
9
+ export function defaultAction() {
10
+ return {
11
+ type: types.DEFAULT_ACTION,
12
+ };
13
+ }
14
+
15
+ export function getTemplates(channel) {
16
+ return {
17
+ type: types.GET_TEMPLATES_REQUEST,
18
+ channel,
19
+ };
20
+ }
@@ -0,0 +1,8 @@
1
+ /*
2
+ *
3
+ * ChannelTemplates constants
4
+ *
5
+ */
6
+
7
+ export const DEFAULT_ACTION = 'app/ChannelTemplates/DEFAULT_ACTION';
8
+
@@ -0,0 +1,47 @@
1
+ /*
2
+ *
3
+ * ChannelTemplates
4
+ *
5
+ */
6
+
7
+ import PropTypes from 'prop-types';
8
+
9
+ import React from 'react';
10
+ import { connect } from 'react-redux';
11
+ import { FormattedMessage } from 'react-intl';
12
+ import { bindActionCreators } from 'redux';
13
+ import { createStructuredSelector } from 'reselect';
14
+ import makeSelectChannelTemplates from './selectors';
15
+ import * as actions from './actions';
16
+ import messages from './messages';
17
+
18
+
19
+ export class ChannelTemplates extends React.Component { // eslint-disable-line react/prefer-stateless-function
20
+ componentDidMount() {
21
+ this.props.actions.getTemplates(this.props.channel);
22
+ }
23
+ render() {
24
+ return (
25
+ <CardGrid
26
+ cardDataList={[]}
27
+ />
28
+ );
29
+ }
30
+ }
31
+
32
+ ChannelTemplates.propTypes = {
33
+ actions: PropTypes.object.isRequired,
34
+ channel: PropTypes.string,
35
+ };
36
+
37
+ const mapStateToProps = createStructuredSelector({
38
+ ChannelTemplates: makeSelectChannelTemplates(),
39
+ });
40
+
41
+ function mapDispatchToProps(dispatch) {
42
+ return {
43
+ actions: bindActionCreators(actions, dispatch),
44
+ };
45
+ }
46
+
47
+ export default connect(mapStateToProps, mapDispatchToProps)(ChannelTemplates);
@@ -0,0 +1,13 @@
1
+ /*
2
+ * ChannelTemplates Messages
3
+ *
4
+ * This contains all the text for the ChannelTemplates component.
5
+ */
6
+ import { defineMessages } from 'react-intl';
7
+
8
+ export default defineMessages({
9
+ header: {
10
+ id: 'creatives.containersV2.ChannelTemplates.header',
11
+ defaultMessage: 'This is ChannelTemplates container !',
12
+ },
13
+ });
@@ -0,0 +1,34 @@
1
+ /*
2
+ *
3
+ * ChannelTemplates reducer
4
+ *
5
+ */
6
+
7
+ import { fromJS } from 'immutable';
8
+ import * as types from './constants';
9
+
10
+ const initialState = fromJS({
11
+ loadingTemplates: true,
12
+ smsTemplates: [],
13
+ emailTemplates: [],
14
+ wechatTemplates: [],
15
+ mobilepushTemplates: [],
16
+ });
17
+
18
+ function channelTemplatesReducer(state = initialState, action) {
19
+ const channelTemplates = `${action.channel}Templates`;
20
+ switch (action.type) {
21
+ case types.DEFAULT_ACTION:
22
+ return state;
23
+ case types.GET_TEMPLATES_REQUEST:
24
+ return state.set('loadingTemplates', true);
25
+ case types.GET_TEMPLATES_SUCESS:
26
+ return state.set('loadingTemplates', false).set(channelTemplates, action.templates);
27
+ case types.GET_TEMPLATES_FAILURE:
28
+ return state.set('loadingTemplates', false).set('error', action.error);
29
+ default:
30
+ return state;
31
+ }
32
+ }
33
+
34
+ export default channelTemplatesReducer;
@@ -0,0 +1,32 @@
1
+ import { take, takeLatest, call, put, cancel } from 'redux-saga/effects';
2
+ import { LOCATION_CHANGE } from 'react-router-redux';
3
+ import * as Api from '../../services/api';
4
+
5
+
6
+ import * as types from './constants';
7
+ // Individual exports for testing
8
+ export function* defaultSaga() {
9
+ // See example in v2Containers/HomePage/sagas.js
10
+ }
11
+ function* getTemplates(action) {
12
+ try {
13
+ const req = {
14
+ channel: action.channel,
15
+ queryParams: action.query,
16
+ };
17
+ const res = call(Api.getAllTemplates, req);
18
+ put({type: types.GET_TEMPLATES_SUCESS, templates: res.response, channel: action.channel});
19
+ } catch (error) {
20
+ put({type: types.GET_TEMPLATES_FAILURE, error});
21
+ }
22
+ }
23
+ function* getTemplatesWatcher() {
24
+ const watcher = yield takeLatest(types.GET_TEMPLATES_REQUEST, getTemplates);
25
+ yield take(LOCATION_CHANGE);
26
+ yield cancel(watcher);
27
+ }
28
+ // All sagas to be loaded
29
+ export default [
30
+ defaultSaga,
31
+ getTemplatesWatcher,
32
+ ];
@@ -0,0 +1,25 @@
1
+ import { createSelector } from 'reselect';
2
+
3
+ /**
4
+ * Direct selector to the channelTemplates state domain
5
+ */
6
+ const selectChannelTemplatesDomain = () => (state) => state.get('templates');
7
+
8
+ /**
9
+ * Other specific selectors
10
+ */
11
+
12
+
13
+ /**
14
+ * Default selector used by ChannelTemplates
15
+ */
16
+
17
+ const makeSelectChannelTemplates = () => createSelector(
18
+ selectChannelTemplatesDomain(),
19
+ (substate) => substate.toJS()
20
+ );
21
+
22
+ export default makeSelectChannelTemplates;
23
+ export {
24
+ selectChannelTemplatesDomain,
25
+ };
@@ -0,0 +1,82 @@
1
+ import { expectSaga } from "redux-saga-test-plan";
2
+ import { take, cancel, takeLatest } from "redux-saga/effects";
3
+ import * as matchers from "redux-saga-test-plan/matchers";
4
+ import { LOCATION_CHANGE } from "react-router-redux";
5
+ import { throwError } from "redux-saga-test-plan/providers";
6
+ import { createMockTask } from "redux-saga/utils";
7
+ import * as types from "../constants";
8
+ import { watchAiSuggestions, getAiSuggestions } from "../sagas";
9
+ import * as Api from "../../../../services/api";
10
+
11
+ describe("getAiSuggestions saga", () => {
12
+ it("Should handle valid response from api", () => {
13
+ const successCallback = () => {};
14
+ const failureCallback = () => {};
15
+ const action = {
16
+ type: types.GET_AI_SUGGESTIONS,
17
+ prompt: {},
18
+ successCallback,
19
+ failureCallback,
20
+ };
21
+ expectSaga(getAiSuggestions, action)
22
+ .provide([
23
+ [
24
+ matchers.call.fn(Api.getAiSuggestions),
25
+ {
26
+ success: true,
27
+ status: {
28
+ isError: false,
29
+ code: 200,
30
+ message: "success",
31
+ },
32
+ message: "Meta data fetched successfully",
33
+ response: {
34
+ "https://response.com": 1400,
35
+ },
36
+ },
37
+ ],
38
+ [matchers.call.fn(successCallback)],
39
+ ])
40
+ .run();
41
+ });
42
+
43
+ it("Should handles error thrown from api", () => {
44
+ const successCallback = () => {};
45
+ const failureCallback = () => {};
46
+ const action = {
47
+ type: types.GET_AI_SUGGESTIONS,
48
+ prompt: {},
49
+ successCallback,
50
+ failureCallback,
51
+ };
52
+ expectSaga(getAiSuggestions, action)
53
+ .provide([[matchers.call.fn(Api.getAiSuggestions), throwError()]])
54
+ .run();
55
+ });
56
+ });
57
+
58
+ describe("watchAiSuggestions saga", () => {
59
+ let generator = null;
60
+ beforeEach(() => {
61
+ generator = watchAiSuggestions();
62
+ });
63
+ it("Should handle valid response from api", () => {
64
+ const progress1 = generator.next();
65
+ const mockTask = takeLatest(types.GET_AI_SUGGESTIONS, getAiSuggestions);
66
+ expect(progress1.value).toEqual(mockTask);
67
+ const progress2 = generator.next();
68
+ expect(progress2.value).toEqual(take(LOCATION_CHANGE));
69
+ });
70
+
71
+ it("Should handle LOCATION_CHANGE action and cancel the watcher", () => {
72
+ generator = watchAiSuggestions();
73
+ const mockTask = createMockTask();
74
+
75
+ expect(generator.next().value).toEqual(
76
+ takeLatest(types.GET_AI_SUGGESTIONS, getAiSuggestions)
77
+ );
78
+ expect(generator.next(mockTask).value).toEqual(take(LOCATION_CHANGE));
79
+ expect(generator.next().value).toEqual(cancel(mockTask));
80
+ expect(generator.next().done).toEqual(true);
81
+ });
82
+ });
@@ -30,6 +30,7 @@ export class TagList extends React.Component { // eslint-disable-line react/pref
30
30
  super(props);
31
31
  this.state = {
32
32
  loading: false,
33
+ tags: []
33
34
  };
34
35
  this.renderTags = this.renderTags.bind(this);
35
36
  this.populateTags = this.populateTags.bind(this);
@@ -42,6 +43,34 @@ export class TagList extends React.Component { // eslint-disable-line react/pref
42
43
 
43
44
  }
44
45
 
46
+ componentDidUpdate(prevProps) {
47
+ if (this.props.tags !== prevProps.tags || this.props.injectedTags !== prevProps.injectedTags || this.props.selectedOfferDetails !== prevProps.selectedOfferDetails) {
48
+ this.setState({
49
+ tags: this.generateTags(this.props),
50
+ });
51
+ }
52
+ }
53
+
54
+ generateTags(props) {
55
+ let tags = {};
56
+ let injectedTags = {};
57
+ if (props.injectedTags && !_.isEmpty(props.injectedTags)) {
58
+ const formattedInjectedTags = handleInjectedData(
59
+ props.injectedTags,
60
+ scope
61
+ );
62
+ injectedTags = this.transformInjectedTags(formattedInjectedTags);
63
+ }
64
+ if (props.tags && props.tags.length > 0) {
65
+ tags = this.populateTags(props.tags);
66
+ console.log('populating tags', Object.keys(tags || {}).length);
67
+ }
68
+ if (props.selectedOfferDetails && !_.isEmpty(props.selectedOfferDetails) && (tags && tags.coupon)) {
69
+ this.transformCouponTags(props.selectedOfferDetails, tags);
70
+ }
71
+ return _.merge( {}, tags, injectedTags);
72
+ }
73
+
45
74
  componentWillReceiveProps(nextProps) {
46
75
  if (_.isEmpty(this.props.injectedTags) && _.isEmpty(this.props.tags)) {
47
76
  this.setState({loading: true});
@@ -54,6 +83,7 @@ export class TagList extends React.Component { // eslint-disable-line react/pref
54
83
 
55
84
  this.setState({loading: false});
56
85
  }
86
+
57
87
  }
58
88
 
59
89
  onSelect = (selectedKeys) => {
@@ -225,29 +255,11 @@ export class TagList extends React.Component { // eslint-disable-line react/pref
225
255
  }
226
256
 
227
257
  render() {
228
- let tags = {};
229
- let injectedTags = {};
230
- if (this.props.injectedTags && !_.isEmpty(this.props.injectedTags)) {
231
- const formattedInjectedTags = handleInjectedData(
232
- this.props.injectedTags,
233
- scope
234
- );
235
- injectedTags = this.transformInjectedTags(formattedInjectedTags);
236
- }
237
- if (this.props.tags && this.props.tags.length > 0) {
238
- tags = this.populateTags(this.props.tags);
239
- console.log('populating tags', Object.keys(tags || {}).length);
240
- }
241
- if (this.props.selectedOfferDetails && !_.isEmpty(this.props.selectedOfferDetails) && (tags && tags.coupon)) {
242
- this.transformCouponTags(this.props.selectedOfferDetails, tags);
243
- }
244
- tags = _.merge( {}, tags, injectedTags);
245
- console.log('merged tags', Object.keys(tags || {}).length);
246
258
  return (
247
259
  <div className={this.props.className ? this.props.className : ''}>
248
260
  <CapTagList
249
261
  loading={this.state.loading}
250
- tags={tags}
262
+ tags={this.state.tags}
251
263
  onSelect={this.onSelect}
252
264
  label={this.props.label}
253
265
  visibleTaglist={this.props.visibleTaglist}