@hellotext/hellotext 1.4.1 → 1.5.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 (91) hide show
  1. package/.babelrc +6 -3
  2. package/.github/workflows/ci.yml +1 -1
  3. package/.prettierignore +2 -0
  4. package/README.md +30 -170
  5. package/__tests__/api/sessions_test.js +12 -0
  6. package/__tests__/builders/input_builder_test.js +117 -0
  7. package/__tests__/{event_emitter_test.js → core/event_test.js} +26 -6
  8. package/__tests__/hellotext_test.js +12 -0
  9. package/__tests__/models/business_test.js +26 -0
  10. package/__tests__/models/cookies_test.js +25 -0
  11. package/__tests__/models/form_collection_test.js +100 -0
  12. package/__tests__/models/form_test.js +29 -0
  13. package/__tests__/models/query_test.js +97 -0
  14. package/dist/hellotext.js +1 -1
  15. package/docs/forms.md +127 -0
  16. package/docs/tracking.md +155 -0
  17. package/lib/api/businesses.js +45 -0
  18. package/lib/api/events.js +54 -0
  19. package/lib/api/forms.js +65 -0
  20. package/lib/api/index.js +51 -0
  21. package/lib/api/response.js +57 -0
  22. package/lib/api/sessions.js +46 -0
  23. package/lib/api/submissions.js +61 -0
  24. package/lib/api.js +73 -0
  25. package/lib/builders/inputBuilder.js +97 -0
  26. package/lib/builders/input_builder.js +61 -0
  27. package/lib/builders/logo_builder.js +38 -0
  28. package/lib/builders/otp_builder.js +39 -0
  29. package/lib/controllers/form_controller.js +139 -0
  30. package/lib/controllers/otp_controller.js +114 -0
  31. package/lib/core/configuration.js +37 -0
  32. package/lib/core/event.js +73 -0
  33. package/lib/core/index.js +20 -0
  34. package/lib/errors/index.js +19 -0
  35. package/lib/errors/invalid_event.js +34 -0
  36. package/lib/errors/not_initialized_error.js +34 -0
  37. package/lib/event.js +159 -32
  38. package/lib/eventEmitter.js +139 -46
  39. package/lib/forms.js +133 -0
  40. package/lib/hellotext.js +53 -105
  41. package/lib/index.js +7 -0
  42. package/lib/locales/en.js +20 -0
  43. package/lib/locales/es.js +20 -0
  44. package/lib/locales/index.js +14 -0
  45. package/lib/models/business.js +57 -0
  46. package/lib/models/configuration.js +66 -0
  47. package/lib/models/cookies.js +34 -0
  48. package/lib/models/form.js +148 -0
  49. package/lib/models/form_collection.js +96 -0
  50. package/lib/models/index.js +40 -0
  51. package/lib/models/query.js +51 -0
  52. package/lib/models/step.js +45 -0
  53. package/lib/query.js +65 -32
  54. package/lib/response.js +82 -42
  55. package/package.json +6 -3
  56. package/src/api/businesses.js +18 -0
  57. package/src/api/events.js +24 -0
  58. package/src/api/forms.js +30 -0
  59. package/src/api/index.js +24 -0
  60. package/src/{response.js → api/response.js} +9 -4
  61. package/src/api/sessions.js +23 -0
  62. package/src/api/submissions.js +30 -0
  63. package/src/builders/input_builder.js +55 -0
  64. package/src/builders/logo_builder.js +25 -0
  65. package/src/builders/otp_builder.js +42 -0
  66. package/src/controllers/form_controller.js +105 -0
  67. package/src/controllers/otp_controller.js +77 -0
  68. package/src/core/configuration.js +16 -0
  69. package/src/core/event.js +54 -0
  70. package/src/core/index.js +2 -0
  71. package/src/errors/index.js +2 -0
  72. package/src/hellotext.js +50 -72
  73. package/src/index.js +10 -0
  74. package/src/locales/en.js +13 -0
  75. package/src/locales/es.js +13 -0
  76. package/src/locales/index.js +7 -0
  77. package/src/models/business.js +41 -0
  78. package/src/models/cookies.js +16 -0
  79. package/src/models/form.js +133 -0
  80. package/src/models/form_collection.js +81 -0
  81. package/src/models/index.js +5 -0
  82. package/src/models/query.js +33 -0
  83. package/styles/index.css +32 -0
  84. package/webpack.config.js +15 -9
  85. package/__tests__/event_test.js +0 -21
  86. package/__tests__/query_test.js +0 -13
  87. package/src/event.js +0 -15
  88. package/src/eventEmitter.js +0 -28
  89. package/src/query.js +0 -21
  90. /package/src/errors/{invalidEvent.js → invalid_event.js} +0 -0
  91. /package/src/errors/{notInitializedError.js → not_initialized_error.js} +0 -0
package/.babelrc CHANGED
@@ -10,9 +10,12 @@
10
10
  ]
11
11
  ],
12
12
  "plugins": [
13
- ["@babel/plugin-proposal-private-methods", {
14
- "loose": true
15
- }],
13
+ [
14
+ "@babel/plugin-proposal-private-methods",
15
+ {
16
+ "loose": true
17
+ }
18
+ ],
16
19
  ["@babel/plugin-proposal-private-property-in-object", { "loose": true }],
17
20
  ["@babel/plugin-proposal-class-properties", { "loose": true }],
18
21
  "@babel/plugin-transform-classes"
@@ -13,7 +13,7 @@ jobs:
13
13
  runs-on: ubuntu-latest
14
14
  strategy:
15
15
  matrix:
16
- node: ['16']
16
+ node: ['20', '22']
17
17
  name: Node ${{ matrix.node }}
18
18
  steps:
19
19
  - uses: actions/checkout@v2
@@ -0,0 +1,2 @@
1
+ __tests__
2
+ dist
package/README.md CHANGED
@@ -1,8 +1,14 @@
1
1
  # Hellotext.js
2
2
 
3
- Track the events happening on your site to [Hellotext](https://www.hellotext.com) in real-time with this library.
3
+ Official [Hellotext](https://www.hellotext.com) (client-side) JavaScript library.
4
4
 
5
- ## Installation
5
+ This library allows you the following,
6
+
7
+ - Track events happening on your site to [Hellotext](https://www.hellotext.com) in real-time.
8
+ - Use Hellotext Forms to dynamically collect data from your customers based on your specific business requirements.
9
+
10
+
11
+ ## Installation
6
12
 
7
13
  ### Using NPM
8
14
 
@@ -21,7 +27,7 @@ yarn add @hellotext/hellotext
21
27
  Import the library into your app.
22
28
 
23
29
  ```javascript
24
- import Hellotext from "@hellotext/hellotext";
30
+ import Hellotext from '@hellotext/hellotext'
25
31
  ```
26
32
 
27
33
  Initialize the library passing the public `HELLOTEXT_BUSINESS_ID` identifier that represents the business.
@@ -29,165 +35,17 @@ Initialize the library passing the public `HELLOTEXT_BUSINESS_ID` identifier tha
29
35
  You can find it from the business's settings page.
30
36
 
31
37
  ```javascript
32
- Hellotext.initialize("HELLOTEXT_BUSINESS_ID");
38
+ Hellotext.initialize('HELLOTEXT_BUSINESS_ID')
33
39
  ```
34
40
 
35
41
  Failing to initialize the class before calling any other method will throw a `NotInitializedError`.
36
42
 
37
- ## Usage
38
-
39
- Tracking events is straightforward and perhaps the simplest example is tracking a page view:
40
-
41
- ```javascript
42
- Hellotext.track("page.viewed");
43
- ```
44
-
45
- In the example above only the name of the action is required.
46
-
47
- ### Handling Responses
48
-
49
- The `track` method returns a Promise that can be `await`ed using the async/await syntax. Or using `.then` on the returned Promise.
50
-
51
- ```javascript
52
- const response = await Hellotext.track("page.viewed");
53
- ```
54
-
55
- The return of the `Hellotext.track` method is an instance of a `Response` object that ships with the package. You can check the status of the response via methods, like:
56
-
57
- ```javascript
58
- if(response.failed) {
59
- console.log("failed because", response.data)
60
- }
61
-
62
- if(response.succeeded) {
63
- console.log("success")
64
- console.log(response.data) // { status: "received" }
65
- }
66
- ```
67
-
68
- ### Parameters
69
-
70
- The parameters passed to the action must be a valid set of parameters as described in
71
- [Tracking Actions](https://www.hellotext.com/api#tracking).
72
-
73
- #### URL Parameter
74
-
75
- The library takes care of handling the `url` parameter with the current URL automatically and is not required to specify it explicitly.
76
- If you want to provide another url, you can pass a `url` key in the params object when tracking an event.
77
-
78
- ```javascript
79
- Hellotext.track("page.viewed", {
80
- url: "www.example.org"
81
- });
82
- ```
83
-
84
- ### Errors
85
-
86
- Failing to provide valid set of parameters will result in an error object being returned, describing the parameters that did not satisfy the rules.
87
-
88
- ```javascript
89
- const response = await Hellotext.track("app.installed", { app_parameters: { name: "My App" }})
90
-
91
- console.log(response.data)
92
- ```
93
-
94
- yields
95
-
96
- ```javascript
97
- {
98
- errors: [
99
- {
100
- type: 'parameter_not_unique',
101
- parameter: 'name',
102
- description: 'The value must be unique and it is already present in another object of the same type.'
103
- },
104
- ]
105
- }
106
- ```
107
-
108
- For a complete list of errors types. See [Error Types](https://www.hellotext.com/api#errors)
109
-
110
- ### Associated objects
111
-
112
- Generally, most actions also require an associated object. These can be of type [`app`](https://www.hellotext.com/api#apps), [`coupon`](https://www.hellotext.com/api#coupons), [`form`](https://www.hellotext.com/api#forms), [`order`](https://www.hellotext.com/api#orders), [`product`](https://www.hellotext.com/api#products) and [`refund`](https://www.hellotext.com/api#refunds).
113
- Aside from [Custom Actions](https://www.hellotext.com/api#create_an_action), which don't require the trackable to be present.
114
-
115
-
116
- You can create the associated object directly by defining its parameters in a hash:
117
-
118
- ```javascript
119
- Hellotext.track("order.placed", {
120
- amount: 395.00,
121
- currency: "USD",
122
- order_parameters: {
123
- "amount": "395.00",
124
- "reference": "654321",
125
- }
126
- });
127
- ```
128
-
129
- If you want to reuse existing objects, you must pass the identifier of an existing associated object. For example, to track a product purchase the identifier of a previously created product object as the `product`.
130
- For more information about identifiers, view the [Tracking API](https://www.hellotext.com/api#tracking)
131
-
132
- ```javascript
133
- Hellotext.track("product.purchased", {
134
- amount: 395.00,
135
- currency: "USD",
136
- product: "erA2RAXE"
137
- });
138
- ```
139
-
140
- ## List of actions
141
-
142
- The following is a complete list of built-in actions and their required associated objects.
143
-
144
- | Action | Description | Required Parameter |
145
- |-----------------------|----------------------------------------------------------| --- |
146
- | **app.installed** | An app was installed. | `app` or [app_parameters](https://www.hellotext.com/api#app)
147
- | **app.removed** | An app was removed. | `app` or [app_parameters](https://www.hellotext.com/api#app)
148
- | **app.spent** | A customer spent on an app. | `app` or [app_parameters](https://www.hellotext.com/api#app)
149
- | **cart.abandoned** | A cart was abandoned. | `product` or [product_parameters](https://www.hellotext.com/api#products)
150
- | **cart.added** | Added an item to the cart. | `product` or [product_parameters](https://www.hellotext.com/api#products)
151
- | **cart.removed** | Removed an item from the cart. | `product` or [product_parameters](https://www.hellotext.com/api#products)
152
- | **coupon.redeemed** | A coupon was redeem by a customer. | `coupon` or [coupon_parameters](https://www.hellotext.com/api#coupons)
153
- | **form.completed** | A form was completed by the customer. | `form` or [form_parameters](https://www.hellotext.com/api#forms)
154
- | **order.placed** | Order has been placed. | `order` or [order_parameters](https://www.hellotext.com/api#orders)
155
- | **order.confirmed** | Order has been confirmed by you. | `order` or [order_parameters](https://www.hellotext.com/api#orders)
156
- | **order.cancelled** | Order has been cancelled either by you or your customer. | `order` or [order_parameters](https://www.hellotext.com/api#orders)
157
- | **order.shipped** | Order has been shipped to your customer. | `order` or [order_parameters](https://www.hellotext.com/api#orders)
158
- | **order.delivered** | Order has been delivered to your customer. | `order` or [order_parameters](https://www.hellotext.com/api#orders)
159
- | **page.viewed** | A page was viewed by a customer. | `url`
160
- | **product.purchased** | A product has been purchased. | `product` or [product_parameters](https://www.hellotext.com/api#products)
161
- | **product.viewed** | A product page has been viewed. | `product` or [product_parameters](https://www.hellotext.com/api#products)
162
- | **refund.requested** | A customer requested a refund. | `refund` or [refund_parameters](https://www.hellotext.com/api#refunds)
163
- | **refund.received** | A refund was issued by you to your customer. | `refund` or [refund_parameters](https://www.hellotext.com/api#refunds)
164
-
165
- You can also create your **[own defined actions](https://www.hellotext.com/api#actions)**.
166
-
167
- ## Additional Properties
168
-
169
- You can include additional attributes to the tracked event, additional properties must be included inside the `metadata` object:
170
-
171
- ```javascript
172
- Hellotext.track("product.purchased", {
173
- amount: 0.20,
174
- currency: "USD",
175
- metadata: {
176
- myProperty: "custom"
177
- },
178
- tracked_at: 1665684173
179
- });
180
- ```
181
-
182
- ### List of additional attributes
43
+ ## Documentation
183
44
 
184
- | Property | Description | Type | Default |
185
- | --- | --- | --- | --- |
186
- | **amount** | Monetary amount that represents the revenue associated to this tracked event. | float | `0`
187
- | **currency** | Currency for the `amount` given in ISO 4217 format. | currency | `USD`
188
- | **metadata** | Set of key-value pairs that you can attach to an event. This can be useful for storing additional information about the object in a structured format. | hash | `{}`
189
- | **tracked_at** | Original date when the event happened. This is useful if you want to record an event that happened in the past. If no value is provided its value will be the same from `created_at`. | epoch | `null`
45
+ Learn how to leverage the library to track events and collect forms.
190
46
 
47
+ - [Tracking Events](/docs/tracking.md)
48
+ - [Forms](/docs/forms.md)
191
49
 
192
50
  ## Events
193
51
 
@@ -209,54 +67,56 @@ Hellotext.removeEventListener(eventName, callback)
209
67
  ### List of events
210
68
 
211
69
  - `session-set`: This event is fired when the session value for `Hellotext.session` is set. Either through an API request, or if the session was found in the cookie.
70
+ - `forms:collected` This event is fired when forms are collected. The callback will receive the array of forms collected.
71
+ - `form:completed` This event is fired when a form has been completed. A form is completed when the user fills all required inputs and verifies their OTP(One-Time Password). The callback will receive the form object that was completed, alongside the data the user filled in the form.
212
72
 
213
73
  ## Understanding Sessions
214
74
 
215
- The library looks for a session identifier present on the `hellotext_session` query parameter. If the session is not present as a cookie neither it will create a new random session identifier, you can disable this default behaviour via the configuration, see [Configuration Options](#configuration-options) for more information.
216
- The session is automatically sent to Hellotext any time the `Hellotext.track` method is called.
75
+ The library looks for a session identifier present on the `hellotext_session` query parameter. If the session is not present as a cookie neither it will create a new random session identifier, you can disable this default behaviour via the configuration, see [Configuration Options](#configuration-options) for more information.
76
+ The session is automatically sent to Hellotext any time the `Hellotext.track` method is called.
217
77
 
218
78
  Short links redirections attaches a session identifier to the destination url as `hellotext_session` query parameter. This will identify all the events back to the customer who opened the link.
219
79
 
220
80
  ### Get session
221
81
 
222
- It is possible to obtain the current session by simply calling `Hellotext.session`.
82
+ It is possible to obtain the current session by simply calling `Hellotext.session`.
223
83
 
224
84
  ```javascript
225
85
  await Hellotext.session
226
86
  // Returns bBJn9vR15yPaYkWmR2QK0jopMeNxrA6l
227
87
  ```
228
88
 
229
- If the session has not been set yet, the result returned will be `undefined`.
89
+ If the session has not been set yet, the result returned will be `undefined`.
230
90
  You can check whether the session has been set or not by calling `Hellotext.isInitialized`.
231
91
 
232
92
  ```javascript
233
- if(Hellotext.isInitialized) {
234
- console.log("session is present")
93
+ if (Hellotext.isInitialized) {
94
+ console.log('session is present')
235
95
  } else {
236
- console.log("session has not been set")
96
+ console.log('session has not been set')
237
97
  }
238
98
  ```
239
99
 
240
100
  Moreover, you can hook in and listen for the session being set, such that when it's set, you're notified about the change, like so
241
101
 
242
102
  ```javascript
243
- Hellotext.on("session-set", (session) => {
244
- console.log("session is: ", session)
103
+ Hellotext.on('session-set', session => {
104
+ console.log('session is: ', session)
245
105
  })
246
106
  ```
247
107
 
248
108
  You may want to store the session on your backend when customers are unidentified so you can later [attach it to a profile](https://www.hellotext.com/api#attach_session) when it becomes known.
249
109
 
250
- ### Configuration
110
+ ### Configuration
251
111
 
252
- When initializing the library, you may pass an optional configuration object as the second argument.
112
+ When initializing the library, you may pass an optional configuration object as the second argument.
253
113
 
254
114
  ```javascript
255
- Hellotext.initialize("HELLOTEXT_BUSINESS_ID", configurationOptions);
115
+ Hellotext.initialize('HELLOTEXT_BUSINESS_ID', configurationOptions)
256
116
  ```
257
117
 
258
118
  #### Configuration Options
259
119
 
260
120
  | Property | Description | Type | Default |
261
- |---------------------|------------------------------------------------------------------------------------------------------------------|---------| --- |
262
- | autogenerateSession | Whether the library should automatically generate a session when no session is found in the query or the cookies | Boolean | true
121
+ | ------------------- | ---------------------------------------------------------------------------------------------------------------- | ------- | ------- |
122
+ | autogenerateSession | Whether the library should automatically generate a session when no session is found in the query or the cookies | Boolean | true |
@@ -0,0 +1,12 @@
1
+ import API from '../../src/api/sessions'
2
+
3
+ beforeEach(() => {
4
+ global.fetch = jest.fn().mockResolvedValue({ json: jest.fn().mockResolvedValue({ id: 1 }) })
5
+ })
6
+
7
+ describe('create', () => {
8
+ it('creates a session object', async () => {
9
+ const response = await new API('M01az53K').create()
10
+ expect(response.id).toEqual(1)
11
+ })
12
+ })
@@ -0,0 +1,117 @@
1
+ /**
2
+ * @jest-environment jsdom
3
+ */
4
+
5
+ import Hellotext from '../../src/hellotext'
6
+ import { InputBuilder } from '../../src/builders/input_builder'
7
+
8
+ describe('when the input is first_name', () => {
9
+ const data = {
10
+ label: 'First Name',
11
+ type: 'text',
12
+ required: true,
13
+ placeholder: 'Enter your first name',
14
+ kind: 'first_name',
15
+ }
16
+
17
+ it('builds an article element that contains a label and input elements', () => {
18
+ const article = InputBuilder.build(data)
19
+ const label = article.querySelector('label')
20
+ const input = article.querySelector('input')
21
+
22
+ expect(label.innerText).toEqual('First Name')
23
+ expect(label.getAttribute('for')).toEqual('first_name')
24
+
25
+ expect(input.id).toEqual('first_name')
26
+ expect(input.type).toEqual('text')
27
+ expect(input.required).toEqual(true)
28
+ expect(input.placeholder).toEqual('Enter your first name')
29
+ expect(input.name).toEqual('first_name')
30
+ })
31
+ })
32
+
33
+ describe('when the input is last_name', () => {
34
+ const data = {
35
+ label: 'Last Name',
36
+ type: 'text',
37
+ required: false,
38
+ placeholder: 'Enter your last name',
39
+ kind: 'last_name',
40
+ }
41
+
42
+ it('builds an article element that contains a label and input elements', () => {
43
+ const article = InputBuilder.build(data)
44
+ const label = article.querySelector('label')
45
+ const input = article.querySelector('input')
46
+
47
+ expect(label.innerText).toEqual('Last Name')
48
+ expect(label.getAttribute('for')).toEqual('last_name')
49
+
50
+ expect(input.id).toEqual('last_name')
51
+ expect(input.type).toEqual('text')
52
+ expect(input.required).toEqual(false)
53
+ expect(input.placeholder).toEqual('Enter your last name')
54
+ expect(input.name).toEqual('last_name')
55
+ })
56
+ })
57
+
58
+ describe('when the input belongs to a property', () => {
59
+ const data = {
60
+ label: 'Email',
61
+ type: 'email',
62
+ required: true,
63
+ placeholder: 'Enter your email',
64
+ kind: 'email',
65
+ property: 'xybz',
66
+ }
67
+
68
+ it('builds an article element that contains a label and input elements', () => {
69
+ const article = InputBuilder.build(data)
70
+ const label = article.querySelector('label')
71
+ const input = article.querySelector('input')
72
+
73
+ expect(label.innerText).toEqual('Email')
74
+ expect(label.getAttribute('for')).toEqual('email')
75
+
76
+ expect(input.id).toEqual('email')
77
+ expect(input.type).toEqual('email')
78
+ expect(input.required).toEqual(true)
79
+ expect(input.placeholder).toEqual('Enter your email')
80
+ expect(input.name).toEqual('email')
81
+ })
82
+ })
83
+
84
+ describe('when the input is a phone number', () => {
85
+ const data = {
86
+ label: 'Phone',
87
+ type: 'tel',
88
+ required: true,
89
+ placeholder: 'Enter your phone number',
90
+ kind: 'phone',
91
+ }
92
+
93
+ beforeEach(() => {
94
+ Hellotext.business = {
95
+ country: {
96
+ prefix: '598',
97
+ code: 'UY',
98
+ }
99
+ }
100
+ })
101
+
102
+ it('builds an article element that contains a label and input elements', () => {
103
+ const article = InputBuilder.build(data)
104
+ const label = article.querySelector('label')
105
+ const input = article.querySelector('input')
106
+
107
+ expect(label.innerText).toEqual('Phone')
108
+ expect(label.getAttribute('for')).toEqual('phone')
109
+
110
+ expect(input.id).toEqual('phone')
111
+ expect(input.type).toEqual('tel')
112
+ expect(input.required).toEqual(true)
113
+ expect(input.placeholder).toEqual('Enter your phone number')
114
+ expect(input.name).toEqual('phone')
115
+ expect(input.value).toEqual('+598')
116
+ })
117
+ })
@@ -1,7 +1,27 @@
1
- import EventEmitter from "../src/eventEmitter"
1
+ import Event from "../../src/core/event"
2
+
3
+ describe(".valid", function () {
4
+ it("is true when event name is a valid defined name", () => {
5
+ expect(Event.valid("session-set")).toEqual(true)
6
+ });
7
+
8
+ it("is false when event name is not defined", () => {
9
+ expect(Event.valid("undefined-event")).toEqual(false)
10
+ });
11
+ });
12
+
13
+ describe(".invalid", () => {
14
+ it("is true when event name is not defined", () => {
15
+ expect(Event.invalid("undefined-event")).toEqual(true)
16
+ });
17
+
18
+ it("is false when event name is a valid defined name", () => {
19
+ expect(Event.invalid("session-set")).toEqual(false)
20
+ });
21
+ });
2
22
 
3
23
  describe("#addSubscriber", function () {
4
- const instance = new EventEmitter()
24
+ const instance = new Event()
5
25
 
6
26
  it("adds the callback to the list of subscribers for an event", () => {
7
27
  const callback = (session) => {}
@@ -11,7 +31,7 @@ describe("#addSubscriber", function () {
11
31
  });
12
32
 
13
33
  describe("#removeSubscriber", function () {
14
- const instance = new EventEmitter()
34
+ const instance = new Event()
15
35
 
16
36
  it("removes the callback from the list of subscribers for an event", () => {
17
37
  const callback = (session) => {}
@@ -22,14 +42,14 @@ describe("#removeSubscriber", function () {
22
42
  });
23
43
  });
24
44
 
25
- describe("#emit", () => {
26
- const instance = new EventEmitter()
45
+ describe("#dispatch", () => {
46
+ const instance = new Event()
27
47
 
28
48
  it("notifies the listeners for an event", () => {
29
49
  const callback = jest.fn()
30
50
 
31
51
  instance.addSubscriber("session-set", callback)
32
- instance.emit("session-set", "session_payload")
52
+ instance.dispatch("session-set", "session_payload")
33
53
 
34
54
  expect(callback).toHaveBeenCalledTimes(1)
35
55
  });
@@ -3,6 +3,7 @@
3
3
  */
4
4
 
5
5
  import Hellotext from "../src/hellotext";
6
+ import { Business } from "../src/models"
6
7
 
7
8
  const getCookieValue = name => document.cookie.match('(^|;)\\s*' + name + '\\s*=\\s*([^;]+)')?.pop()
8
9
 
@@ -10,6 +11,10 @@ const expireSession = () => {
10
11
  document.cookie = "hello_session=;expires=Thu, 01 Jan 1970 00:00:00 GMT"
11
12
  }
12
13
 
14
+ beforeEach(() => {
15
+ Business.prototype.fetchPublicData = jest.fn().mockResolvedValue({ whitelist: 'disabled' })
16
+ })
17
+
13
18
  afterEach(() => {
14
19
  jest.clearAllMocks();
15
20
  });
@@ -204,3 +209,10 @@ describe("when hello_preview query parameter is present", () => {
204
209
  });
205
210
  })
206
211
  })
212
+
213
+ describe('setSession', () => {
214
+ it('sets the session', () => {
215
+ Hellotext.setSession("12345")
216
+ expect(Hellotext.session).toEqual("12345")
217
+ })
218
+ })
@@ -0,0 +1,26 @@
1
+ /**
2
+ * @jest-environment jsdom
3
+ */
4
+
5
+
6
+ import { Business } from '../../src/models'
7
+
8
+ describe('enabledWhitelist', () => {
9
+ beforeEach(() => {
10
+ global.fetch = jest.fn().mockResolvedValue({ json: jest.fn().mockResolvedValue({}) })
11
+ })
12
+
13
+ it('is true when the whitelist is an array', () => {
14
+ const business = new Business('123')
15
+ business.data = { whitelist: ['www.example.com'] }
16
+
17
+ expect(business.enabledWhitelist).toEqual(true)
18
+ })
19
+
20
+ it('is false when the whitelist is a string', () => {
21
+ const business = new Business('123')
22
+ business.data = { whitelist: 'disabled' }
23
+
24
+ expect(business.enabledWhitelist).toEqual(false)
25
+ })
26
+ })
@@ -0,0 +1,25 @@
1
+ /**
2
+ * @jest-environment jsdom
3
+ */
4
+
5
+ import { Cookies } from '../../src/models'
6
+
7
+ beforeEach(() => {
8
+ document.cookie = ''
9
+ })
10
+
11
+ describe('set', () => {
12
+ it('sets the value of a cookie', () => {
13
+ expect(Cookies.get('hello_session')).toEqual(undefined)
14
+
15
+ Cookies.set('hello_session', 'session')
16
+ expect(Cookies.get('hello_session')).toEqual('session')
17
+ })
18
+ })
19
+
20
+ describe('get', () => {
21
+ it('gets the value of a cookie', () => {
22
+ document.cookie = 'hello_session=session'
23
+ expect(Cookies.get('hello_session')).toEqual('session')
24
+ })
25
+ })
@@ -0,0 +1,100 @@
1
+ /**
2
+ * @jest-environment jsdom
3
+ */
4
+
5
+ import { Business, FormCollection } from '../../src/models'
6
+ import Hellotext from '../../src/hellotext'
7
+
8
+ beforeEach(() => {
9
+ Business.prototype.fetchPublicData = jest.fn().mockResolvedValue({ whitelist: 'disabled' })
10
+
11
+ Hellotext.initialize('M01az53K', {
12
+ autogenerateSession: false
13
+ })
14
+ })
15
+
16
+ describe('collect', () => {
17
+ it('does not fetch forms when there are no forms to fetch', async () => {
18
+ const fetch = jest.fn()
19
+ global.fetch = fetch
20
+
21
+ await Hellotext.forms.collect()
22
+
23
+ expect(fetch).not.toHaveBeenCalled()
24
+ })
25
+
26
+ it('fetches forms when there are forms to fetch', async () => {
27
+ global.fetch = jest.fn().mockResolvedValue({ json: jest.fn().mockResolvedValue({ id: 1 }) })
28
+
29
+ document.body.innerHTML = `
30
+ <form data-hello-form="1"></form>
31
+ `
32
+
33
+ await Hellotext.forms.collect()
34
+
35
+ expect(fetch).toHaveBeenCalledTimes(1)
36
+ })
37
+ })
38
+
39
+ describe('add', () => {
40
+ const forms = new FormCollection()
41
+ const form = { id: 1, name: 'Form 1' }
42
+
43
+ it('adds a form to the forms array', () => {
44
+ forms.add(form)
45
+ expect(forms.length).toEqual(1)
46
+ })
47
+
48
+ it('does not add a form that is already in the forms array', () => {
49
+ forms.add(form)
50
+ forms.add(form)
51
+
52
+ expect(forms.length).toEqual(1)
53
+ })
54
+ })
55
+
56
+ describe('getting elements', () => {
57
+ const forms = new FormCollection()
58
+ forms.add({ id: 1, name: 'Form 1' })
59
+ forms.add({ id: 2, name: 'Form 1' })
60
+
61
+ describe('getById', () => {
62
+ it('returns the form with the given id', () => {
63
+ expect(forms.getById(1).id).toEqual(1)
64
+ })
65
+ })
66
+
67
+ describe('getByIndex', () => {
68
+ it('returns the form at the given index', () => {
69
+ expect(forms.getByIndex(1).id).toEqual(2)
70
+ })
71
+ })
72
+ })
73
+
74
+ describe('includes', () => {
75
+ const forms = new FormCollection()
76
+ const form = { id: 1, name: 'Form 1' }
77
+
78
+ it('is true when the form is in the forms array', () => {
79
+ forms.add(form)
80
+ expect(forms.includes(1)).toEqual(true)
81
+ })
82
+
83
+ it('is false when the form is not in the forms array', () => {
84
+ expect(forms.includes(2)).toEqual(false)
85
+ })
86
+ })
87
+
88
+ describe('excludes', () => {
89
+ const forms = new FormCollection()
90
+ const form = { id: 1, name: 'Form 1' }
91
+
92
+ it('is true when the form is not in the forms array', () => {
93
+ expect(forms.excludes(2)).toEqual(true)
94
+ })
95
+
96
+ it('is false when the form is in the forms array', () => {
97
+ forms.add(form)
98
+ expect(forms.excludes(1)).toEqual(false)
99
+ })
100
+ })