@meetelise/chat 1.52.0 → 1.53.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.
@@ -195,6 +195,8 @@ export class TourScheduler extends LitElement {
195
195
 
196
196
  @property({ attribute: false })
197
197
  country: string | null = null;
198
+ @property({ attribute: false })
199
+ buildingState: string | null = null;
198
200
 
199
201
  @property({ attribute: true })
200
202
  currentLeadSource = ""; // the default lead source based on referrer and query params.
@@ -1645,6 +1647,7 @@ export class TourScheduler extends LitElement {
1645
1647
  orgLegalName: this.orgLegalName,
1646
1648
  orgSlug: this.orgSlug,
1647
1649
  country: this.country,
1650
+ buildingState: this.buildingState,
1648
1651
  clickingButtonText: "schedule tour",
1649
1652
  })}
1650
1653
  </div>
@@ -1700,6 +1703,7 @@ export class TourScheduler extends LitElement {
1700
1703
  orgLegalName: this.orgLegalName,
1701
1704
  orgSlug: this.orgSlug,
1702
1705
  country: this.country,
1706
+ buildingState: this.buildingState,
1703
1707
  clickingButtonText: "schedule tour",
1704
1708
  })}
1705
1709
  </div>
@@ -1810,6 +1814,7 @@ export class TourScheduler extends LitElement {
1810
1814
  orgLegalName: this.orgLegalName,
1811
1815
  orgSlug: this.orgSlug,
1812
1816
  country: this.country,
1817
+ buildingState: this.buildingState,
1813
1818
  clickingButtonText: "schedule tour",
1814
1819
  })}
1815
1820
  `
@@ -25,6 +25,8 @@ import { InputStyles } from "./InputStyles";
25
25
  import {
26
26
  CANADA_PRIVACY_POLICY_URL,
27
27
  isCanadianBuilding,
28
+ isQuebecBuilding,
29
+ quebecConsentDisclaimer,
28
30
  } from "../../disclaimers";
29
31
 
30
32
  @customElement("call-us-window")
@@ -173,6 +175,8 @@ export class CallUsWindow extends LitElement {
173
175
  orgLegalName = "";
174
176
  @property({ attribute: false })
175
177
  country: string | null = null;
178
+ @property({ attribute: false })
179
+ buildingState: string | null = null;
176
180
 
177
181
  @property({ attribute: true })
178
182
  private leadSourceClient: LeadSourceClient | null = null;
@@ -321,7 +325,9 @@ export class CallUsWindow extends LitElement {
321
325
  </div>
322
326
 
323
327
  <div class="text-us-window__description">
324
- ${isCanadianBuilding(this.country)
328
+ ${isQuebecBuilding(this.buildingState)
329
+ ? quebecConsentDisclaimer("textUs", this.orgLegalName)
330
+ : isCanadianBuilding(this.country)
325
331
  ? html`By providing your number and clicking send, you consent to
326
332
  receive recurring commercial electronic messages from or on behalf
327
333
  of ${this.orgLegalName} at this number. Messages may be AI or
@@ -121,6 +121,8 @@ export class EmailUsWindow extends LitElement {
121
121
  orgLegalName = "";
122
122
  @property({ attribute: false })
123
123
  country: string | null = null;
124
+ @property({ attribute: false })
125
+ buildingState: string | null = null;
124
126
 
125
127
  @property({ attribute: true })
126
128
  private leadSourceClient: LeadSourceClient | null = null;
@@ -492,6 +494,7 @@ export class EmailUsWindow extends LitElement {
492
494
  orgLegalName: this.orgLegalName,
493
495
  orgSlug: this.orgSlug,
494
496
  country: this.country,
497
+ buildingState: this.buildingState,
495
498
  clickingButtonText: "send",
496
499
  })}
497
500
  </details-window>
@@ -81,6 +81,8 @@ export class Launcher extends LitElement {
81
81
  orgLegalName = "";
82
82
  @property({ attribute: false })
83
83
  country: string | null = null;
84
+ @property({ attribute: false })
85
+ buildingState: string | null = null;
84
86
  @property({ attribute: true })
85
87
  chatCallUsHeader = "";
86
88
  @property({ attribute: false })
@@ -1340,6 +1342,7 @@ export class Launcher extends LitElement {
1340
1342
  .leadSourceMultitouchClient=${this.leadSourceMultitouchClient}
1341
1343
  .orgLegalName=${this.orgLegalName}
1342
1344
  .country=${this.country}
1345
+ .buildingState=${this.buildingState}
1343
1346
  >
1344
1347
  </email-us-window>
1345
1348
  </div>`
@@ -1369,6 +1372,7 @@ export class Launcher extends LitElement {
1369
1372
  .bedroomPreferenceRequired=${this.bedroomPreferenceRequired}
1370
1373
  .orgLegalName=${this.orgLegalName}
1371
1374
  .country=${this.country}
1375
+ .buildingState=${this.buildingState}
1372
1376
  buildingId=${this.buildingId}
1373
1377
  orgId=${this.orgId}
1374
1378
  .leadSourceParams=${this.leadSourceParams}
@@ -1414,6 +1418,7 @@ export class Launcher extends LitElement {
1414
1418
  .leadSourceMultitouchClient=${this.leadSourceMultitouchClient}
1415
1419
  .orgLegalName=${this.orgLegalName}
1416
1420
  .country=${this.country}
1421
+ .buildingState=${this.buildingState}
1417
1422
  ></call-us-window>
1418
1423
  </div>
1419
1424
  `
@@ -1044,6 +1044,7 @@ export class MEChat extends LitElement {
1044
1044
  ?.usesDynamicScheduling ?? false}
1045
1045
  .orgLegalName=${this.buildingWebchatView?.orgLegalName ?? ""}
1046
1046
  .country=${this.buildingWebchatView?.country ?? null}
1047
+ .buildingState=${this.buildingWebchatView?.state ?? null}
1047
1048
  .tourTypeOptions=${this.buildingWebchatView
1048
1049
  ?.tourTypeOptions ?? []}
1049
1050
  .layoutOptions=${this.buildingWebchatView?.layoutOptions ??
@@ -107,21 +107,25 @@ export class PubnubMessage extends LitElement {
107
107
  const allWords = this.preserveHTMLTags(sanitizedLine);
108
108
  if (!allWords) return html``;
109
109
  return html`${allWords.map((word) => {
110
- if (hyperlinks.find((obj) => obj.id === word)) {
111
- // EliseAI made hyperlink
112
- const link = hyperlinks.find((obj) => obj.id === word);
113
- if (!link) return;
114
- imagesToDisplay.push({
115
- redirectTo: link?.link,
116
- source: link?.link,
110
+ const markedLinks = hyperlinks.filter((obj) =>
111
+ word.includes(obj.id)
112
+ );
113
+ if (markedLinks.length) {
114
+ // EliseAI made hyperlink, possibly inside markdown formatting like <b>
115
+ let wordWithLinks = word;
116
+ markedLinks.forEach((link) => {
117
+ imagesToDisplay.push({
118
+ redirectTo: link.link,
119
+ source: link.link,
120
+ });
121
+ wordWithLinks = wordWithLinks.replace(
122
+ link.id,
123
+ this.hyperlinkHTML(link.link, link.hyperlink)
124
+ );
117
125
  });
118
- return html`<a
119
- class="redirect-link"
120
- href="${link?.link}"
121
- target="_blank"
122
- rel="noopener noreferrer"
123
- >${link?.hyperlink}
124
- </a>`;
126
+ return html`${unsafeHTML(
127
+ DOMPurify.sanitize(wordWithLinks, { ADD_ATTR: ["target"] })
128
+ )} `;
125
129
  }
126
130
  if (urlRegex.test(word)) {
127
131
  // general url
@@ -145,6 +149,16 @@ export class PubnubMessage extends LitElement {
145
149
  });
146
150
  }
147
151
 
152
+ private hyperlinkHTML(link: string, hyperlink: string): string {
153
+ const anchor = document.createElement("a");
154
+ anchor.className = "redirect-link";
155
+ anchor.href = link;
156
+ anchor.target = "_blank";
157
+ anchor.rel = "noopener noreferrer";
158
+ anchor.textContent = hyperlink;
159
+ return anchor.outerHTML;
160
+ }
161
+
148
162
  private preserveHTMLTags(text: string): string[] {
149
163
  const tagRegex = /(<\/?[a-z][^>]*>[^<]*<\/?[a-z]*>|[^\s]+[.,?!]?|\S)/gi;
150
164
  const splitTags = text.match(tagRegex);
@@ -12,6 +12,9 @@ const disclaimerStyles = {
12
12
  link: {
13
13
  color: "#4287f5",
14
14
  },
15
+ paragraph: {
16
+ margin: "0 0 4px",
17
+ },
15
18
  };
16
19
 
17
20
  // States where we need to say "you are interacting with generative AI".
@@ -23,6 +26,129 @@ export const isCanadianBuilding = (
23
26
  country: string | null | undefined
24
27
  ): boolean => country === "CA";
25
28
 
29
+ const QUEBEC_PRIVACY_POLICY_URL = "https://eliseai.com/fr/canada-policy";
30
+ const QUEBEC_TERMS_URL = "https://eliseai.com/fr/msgterms";
31
+
32
+ export const isQuebecBuilding = (state: string | null | undefined): boolean =>
33
+ state?.trim().toUpperCase() === "QC";
34
+
35
+ type QuebecConsentForm = "emailUs" | "textUs" | "scheduleTour";
36
+
37
+ const disclaimerLink = (href: string, label: string): TemplateResult =>
38
+ html`<a
39
+ style=${styleMap(disclaimerStyles.link)}
40
+ href=${href}
41
+ target="_blank"
42
+ rel="noopener noreferrer"
43
+ >${label}</a
44
+ >`;
45
+
46
+ const quebecConsentText = (
47
+ form: QuebecConsentForm,
48
+ orgLegalName: string
49
+ ): [TemplateResult, TemplateResult] => {
50
+ const frenchLinks = html`${disclaimerLink(
51
+ QUEBEC_PRIVACY_POLICY_URL,
52
+ "Politique relative à la protection des renseignements personnels"
53
+ )}
54
+ ainsi qu’à ces ${disclaimerLink(QUEBEC_TERMS_URL, "Conditions générales")}`;
55
+ const englishLinks = html`${disclaimerLink(
56
+ QUEBEC_PRIVACY_POLICY_URL,
57
+ "privacy policy"
58
+ )}
59
+ and these ${disclaimerLink(QUEBEC_TERMS_URL, "terms and conditions")}`;
60
+
61
+ if (form === "textUs") {
62
+ return [
63
+ html`Lorsque vous fournissez votre numéro de téléphone et que vous cliquez
64
+ sur Envoyer (send), vous consentez à recevoir des messages électroniques
65
+ et des appels récurrents de nature commerciale de la part de
66
+ ${orgLegalName} ou en son nom à ce numéro, dans la mesure où cela est
67
+ permis par les lois applicables. Les messages peuvent être générés par une
68
+ intelligence artificielle (IA) ou par un humain. Frais std SMS/données
69
+ applicables. Vous consentez à cette ${frenchLinks}, y compris à ce que
70
+ notre fournisseur de services, EliseAI, enregistre et utilise votre numéro
71
+ de téléphone et vos communications afin de générer des réponses et
72
+ d’améliorer ses services. Vous pouvez retirer votre consentement en tout
73
+ temps en vous désabonnant ou en communiquant avec nous.`,
74
+ html`By providing your number and clicking send, you consent to receive
75
+ recurring commercial electronic messages and calls from or on behalf of
76
+ ${orgLegalName} at this number, to the extent permitted by applicable law.
77
+ Messages may be AI or human generated. StdMsg&DataRatesMayApply. You
78
+ consent to this ${englishLinks}, including having your number and
79
+ communications recorded and used by our service provider EliseAI to
80
+ generate responses and improve their services. You may withdraw your
81
+ consent at any time by unsubscribing or contacting us.`,
82
+ ];
83
+ }
84
+
85
+ if (form === "scheduleTour") {
86
+ return [
87
+ html`Lorsque vous fournissez votre numéro de téléphone ou votre adresse
88
+ courriel et que vous cliquez sur Planifier une visite (schedule tour),
89
+ vous consentez à recevoir des messages électroniques et des appels
90
+ récurrents de nature commerciale de la part de ${orgLegalName} ou en son
91
+ nom à ce numéro ou à cette adresse courriel, dans la mesure où cela est
92
+ permis par les lois applicables. Les messages peuvent être générés par une
93
+ intelligence artificielle (IA) ou par un humain. Frais std SMS/données
94
+ applicables. Vous consentez à cette ${frenchLinks}, y compris à ce que
95
+ notre fournisseur de services, EliseAI, enregistre et utilise votre numéro
96
+ de téléphone, votre adresse courriel et vos communications afin de générer
97
+ des réponses et d’améliorer ses services. Vous pouvez retirer votre
98
+ consentement en tout temps en vous désabonnant ou en communiquant avec
99
+ nous.`,
100
+ html`By providing your number and/or email address and clicking schedule
101
+ tour, you consent to receive recurring commercial electronic messages and
102
+ calls from or on behalf of ${orgLegalName} at the provided number and/or
103
+ email address, to the extent permitted by applicable law. Messages may be
104
+ AI or human generated. StdMsg&DataRatesMayApply. You consent to this
105
+ ${englishLinks}, including having your number, email address, and
106
+ communications recorded and used by our service provider EliseAI to
107
+ generate responses and improve their services. You may withdraw your
108
+ consent at any time by unsubscribing or contacting us.`,
109
+ ];
110
+ }
111
+
112
+ return [
113
+ html`Lorsque vous fournissez votre adresse courriel ou votre numéro de
114
+ téléphone et que vous cliquez sur Envoyer (send), vous consentez à recevoir
115
+ des messages électroniques et des appels récurrents de nature commerciale de
116
+ la part de ${orgLegalName} ou en son nom à cette adresse courriel ou à ce
117
+ numéro, dans la mesure où cela est permis par les lois applicables. Les
118
+ messages peuvent être générés par une intelligence artificielle (IA) ou par
119
+ un humain. Vous consentez à cette ${frenchLinks}, y compris à ce que notre
120
+ fournisseur de services, EliseAI, enregistre et utilise votre adresse
121
+ courriel, votre numéro de téléphone et vos communications afin de générer
122
+ des réponses et d’améliorer ses services. Vous pouvez retirer votre
123
+ consentement en tout temps en vous désabonnant ou en communiquant avec nous.`,
124
+ html`By providing your email address and/or number and clicking send, you
125
+ consent to receive recurring commercial electronic messages and calls from
126
+ or on behalf of ${orgLegalName} at that email address and/or number, to the
127
+ extent permitted by applicable law. Messages may be AI or human generated.
128
+ You consent to this ${englishLinks}, including having your email address,
129
+ number, and communications recorded and used by our service provider EliseAI
130
+ to generate responses and improve their services. You may withdraw your
131
+ consent at any time by unsubscribing or contacting us.`,
132
+ ];
133
+ };
134
+
135
+ export const quebecConsentDisclaimer = (
136
+ form: QuebecConsentForm,
137
+ orgLegalName: string,
138
+ hasPhoneNumber = false
139
+ ): TemplateResult => {
140
+ const [french, english] = quebecConsentText(form, orgLegalName);
141
+ const paragraphStyle = styleMap(disclaimerStyles.paragraph);
142
+ return html`<p style=${paragraphStyle}>${french}</p>
143
+ <p style=${paragraphStyle}>${english}</p>
144
+ ${form === "textUs" || hasPhoneNumber
145
+ ? html`<p style=${paragraphStyle}>
146
+ Msg freq varies. Text STOP to opt-out of messages. Text HELP for
147
+ assistance.
148
+ </p>`
149
+ : ""}`;
150
+ };
151
+
26
152
  export const CURRENT_CONSENT_ID = "currentConsentId";
27
153
 
28
154
  const formDisclaimer = ({
@@ -31,6 +157,7 @@ const formDisclaimer = ({
31
157
  orgLegalName,
32
158
  orgSlug,
33
159
  country,
160
+ buildingState,
34
161
  clickingButtonText = "send",
35
162
  }: {
36
163
  buildingName: string;
@@ -39,8 +166,19 @@ const formDisclaimer = ({
39
166
  orgLegalName: string;
40
167
  orgSlug: string;
41
168
  country?: string | null;
169
+ buildingState?: string | null;
42
170
  clickingButtonText?: string;
43
171
  }): TemplateResult => {
172
+ if (isQuebecBuilding(buildingState)) {
173
+ return html`<div style=${styleMap(disclaimerStyles.container)}>
174
+ ${quebecConsentDisclaimer(
175
+ clickingButtonText === "schedule tour" ? "scheduleTour" : "emailUs",
176
+ orgLegalName,
177
+ !!phoneNumberInput
178
+ )}
179
+ </div>`;
180
+ }
181
+
44
182
  const policyUrl = isCanadianBuilding(country)
45
183
  ? CANADA_PRIVACY_POLICY_URL
46
184
  : "http://bit.ly/me_privacy_policy";
@@ -0,0 +1,44 @@
1
+ import { expect } from "@esm-bundle/chai";
2
+ import "../public/dist/index";
3
+ import type { SimpleTextChatMessage } from "./MyPubnub";
4
+ import type { PubnubMessage } from "./WebComponent/pubnub-message";
5
+
6
+ declare const it: (name: string, callback: () => void | Promise<void>) => void;
7
+
8
+ const renderMessage = async (message: string): Promise<ShadowRoot> => {
9
+ const element = document.createElement("pubnub-message") as PubnubMessage;
10
+ element.message = {
11
+ timestamp: 100,
12
+ message,
13
+ isLeadMessage: false,
14
+ chunks: [{ text: message, order: 0, isDone: true }],
15
+ type: "text" as SimpleTextChatMessage["type"],
16
+ };
17
+ document.body.appendChild(element);
18
+ await element.updateComplete;
19
+ await element.updateComplete;
20
+ return element.shadowRoot as ShadowRoot;
21
+ };
22
+
23
+ it("renders bolded and plain markdown links as links to the community", async () => {
24
+ const root = await renderMessage(
25
+ "- **[Alder House](https://www.clg-test.com/alder)** apartment 109\n" +
26
+ "- [Cedar Row](https://www.clg-test.com/cedar) apartment 103"
27
+ );
28
+
29
+ const boldLink = root.querySelector("b a.redirect-link");
30
+ expect(boldLink?.getAttribute("href")).to.equal(
31
+ "https://www.clg-test.com/alder"
32
+ );
33
+ expect(boldLink?.getAttribute("target")).to.equal("_blank");
34
+ expect(boldLink?.textContent?.trim()).to.equal("Alder House");
35
+
36
+ const plainLink = Array.from(root.querySelectorAll("a.redirect-link")).find(
37
+ (anchor) => anchor.textContent?.trim() === "Cedar Row"
38
+ );
39
+ expect(plainLink?.getAttribute("href")).to.equal(
40
+ "https://www.clg-test.com/cedar"
41
+ );
42
+ expect(plainLink?.getAttribute("target")).to.equal("_blank");
43
+ expect(root.textContent).not.to.match(/[0-9a-f]{32}/);
44
+ });
@@ -0,0 +1,161 @@
1
+ import { expect } from "@esm-bundle/chai";
2
+ import { restore, useFakeXMLHttpRequest } from "sinon/pkg/sinon-esm";
3
+ import "../public/dist/index";
4
+
5
+ type Region = { state: string | null; country: string | null };
6
+ type Widget = HTMLElement & { updateComplete: Promise<boolean> };
7
+ type WidgetCase = {
8
+ name: string;
9
+ tag: string;
10
+ props: Record<string, string>;
11
+ propsAfterMount?: Record<string, boolean>;
12
+ french: string;
13
+ english: string;
14
+ ontarioCopy: string;
15
+ newYorkCopy: string;
16
+ newYorkLinks: string[];
17
+ };
18
+
19
+ const ORG = "Test Org Legal Inc.";
20
+ const QUEBEC: Region[] = [
21
+ { state: "QC", country: "CA" },
22
+ { state: "qc", country: null },
23
+ ];
24
+ const ONTARIO: Region = { state: "ON", country: "CA" };
25
+ const NEW_YORK: Region = { state: "NY", country: "US" };
26
+
27
+ const FR_PRIVACY =
28
+ "Politique relative à la protection des renseignements personnels";
29
+ const PHONE_OPT_OUT =
30
+ "Msg freq varies. Text STOP to opt-out of messages. Text HELP for assistance.";
31
+
32
+ const EMAIL_US_FR = `Lorsque vous fournissez votre adresse courriel ou votre numéro de téléphone et que vous cliquez sur Envoyer (send), vous consentez à recevoir des messages électroniques et des appels récurrents de nature commerciale de la part de ${ORG} ou en son nom à cette adresse courriel ou à ce numéro, dans la mesure où cela est permis par les lois applicables. Les messages peuvent être générés par une intelligence artificielle (IA) ou par un humain. Vous consentez à cette ${FR_PRIVACY} ainsi qu’à ces Conditions générales, y compris à ce que notre fournisseur de services, EliseAI, enregistre et utilise votre adresse courriel, votre numéro de téléphone et vos communications afin de générer des réponses et d’améliorer ses services. Vous pouvez retirer votre consentement en tout temps en vous désabonnant ou en communiquant avec nous.`;
33
+ const EMAIL_US_EN = `By providing your email address and/or number and clicking send, you consent to receive recurring commercial electronic messages and calls from or on behalf of ${ORG} at that email address and/or number, to the extent permitted by applicable law. Messages may be AI or human generated. You consent to this privacy policy and these terms and conditions, including having your email address, number, and communications recorded and used by our service provider EliseAI to generate responses and improve their services. You may withdraw your consent at any time by unsubscribing or contacting us.`;
34
+
35
+ const TEXT_US_FR = `Lorsque vous fournissez votre numéro de téléphone et que vous cliquez sur Envoyer (send), vous consentez à recevoir des messages électroniques et des appels récurrents de nature commerciale de la part de ${ORG} ou en son nom à ce numéro, dans la mesure où cela est permis par les lois applicables. Les messages peuvent être générés par une intelligence artificielle (IA) ou par un humain. Frais std SMS/données applicables. Vous consentez à cette ${FR_PRIVACY} ainsi qu’à ces Conditions générales, y compris à ce que notre fournisseur de services, EliseAI, enregistre et utilise votre numéro de téléphone et vos communications afin de générer des réponses et d’améliorer ses services. Vous pouvez retirer votre consentement en tout temps en vous désabonnant ou en communiquant avec nous.`;
36
+ const TEXT_US_EN = `By providing your number and clicking send, you consent to receive recurring commercial electronic messages and calls from or on behalf of ${ORG} at this number, to the extent permitted by applicable law. Messages may be AI or human generated. StdMsg&DataRatesMayApply. You consent to this privacy policy and these terms and conditions, including having your number and communications recorded and used by our service provider EliseAI to generate responses and improve their services. You may withdraw your consent at any time by unsubscribing or contacting us.`;
37
+
38
+ const SCHEDULE_TOUR_FR = `Lorsque vous fournissez votre numéro de téléphone ou votre adresse courriel et que vous cliquez sur Planifier une visite (schedule tour), vous consentez à recevoir des messages électroniques et des appels récurrents de nature commerciale de la part de ${ORG} ou en son nom à ce numéro ou à cette adresse courriel, dans la mesure où cela est permis par les lois applicables. Les messages peuvent être générés par une intelligence artificielle (IA) ou par un humain. Frais std SMS/données applicables. Vous consentez à cette ${FR_PRIVACY} ainsi qu’à ces Conditions générales, y compris à ce que notre fournisseur de services, EliseAI, enregistre et utilise votre numéro de téléphone, votre adresse courriel et vos communications afin de générer des réponses et d’améliorer ses services. Vous pouvez retirer votre consentement en tout temps en vous désabonnant ou en communiquant avec nous.`;
39
+ const SCHEDULE_TOUR_EN = `By providing your number and/or email address and clicking schedule tour, you consent to receive recurring commercial electronic messages and calls from or on behalf of ${ORG} at the provided number and/or email address, to the extent permitted by applicable law. Messages may be AI or human generated. StdMsg&DataRatesMayApply. You consent to this privacy policy and these terms and conditions, including having your number, email address, and communications recorded and used by our service provider EliseAI to generate responses and improve their services. You may withdraw your consent at any time by unsubscribing or contacting us.`;
40
+
41
+ const renderedText = (root: ShadowRoot | null | undefined): string =>
42
+ (root?.textContent ?? "").replace(/\s+/g, " ");
43
+
44
+ const linkHrefs = (root: ShadowRoot | null | undefined): string[] =>
45
+ Array.from(root?.querySelectorAll("a") ?? []).map(
46
+ (a) => `${a.textContent?.trim()} -> ${a.getAttribute("href")}`
47
+ );
48
+
49
+ const expectQuebecConsent = (
50
+ root: ShadowRoot | null | undefined,
51
+ french: string,
52
+ english: string
53
+ ): void => {
54
+ const text = renderedText(root);
55
+ expect(text).to.include(french);
56
+ expect(text).to.include(english);
57
+ expect(text.indexOf(french)).to.be.lessThan(text.indexOf(english));
58
+ expect(text.indexOf(english)).to.be.lessThan(text.indexOf(PHONE_OPT_OUT));
59
+ expect(linkHrefs(root)).to.include.members([
60
+ `${FR_PRIVACY} -> https://eliseai.com/fr/canada-policy`,
61
+ "Conditions générales -> https://eliseai.com/fr/msgterms",
62
+ "privacy policy -> https://eliseai.com/fr/canada-policy",
63
+ "terms and conditions -> https://eliseai.com/fr/msgterms",
64
+ ]);
65
+ };
66
+
67
+ const expectNoFrench = (root: ShadowRoot | null | undefined): void => {
68
+ expect(renderedText(root)).not.to.include("Lorsque vous fournissez");
69
+ };
70
+
71
+ const US_PRIVACY_LINK = "privacy policy -> http://bit.ly/me_privacy_policy";
72
+ const PHONE = "(514) 555-0123";
73
+
74
+ const WIDGETS: WidgetCase[] = [
75
+ {
76
+ name: "Email Us form",
77
+ tag: "email-us-window",
78
+ props: { email: "local.test@example.com", phoneNumber: PHONE },
79
+ french: EMAIL_US_FR,
80
+ english: EMAIL_US_EN,
81
+ ontarioCopy: `By providing your email address and/or number and clicking send, you consent to receive recurring commercial electronic messages and calls from or on behalf of ${ORG} at that email address and/or number. Messages may be AI or human generated.`,
82
+ newYorkCopy: `By providing your number and clicking send, you consent to receive recurring marketing text and voice messages and calls from or on behalf of ${ORG} at this number using artificial voice or an autodialer system.`,
83
+ newYorkLinks: [US_PRIVACY_LINK],
84
+ },
85
+ {
86
+ name: "Text Us form",
87
+ tag: "call-us-window",
88
+ props: { hasTextUsEnabled: "true" },
89
+ french: TEXT_US_FR,
90
+ english: TEXT_US_EN,
91
+ ontarioCopy: `By providing your number and clicking send, you consent to receive recurring commercial electronic messages from or on behalf of ${ORG} at this number.`,
92
+ newYorkCopy: `By providing your number and clicking send, you consent to receive recurring marketing text and voice messages and calls from or on behalf of ${ORG} at this number using artificial voice or an autodialer system.`,
93
+ newYorkLinks: [
94
+ US_PRIVACY_LINK,
95
+ "terms and conditions -> https://eliseai.com/msgterms",
96
+ ],
97
+ },
98
+ {
99
+ name: "tour scheduler",
100
+ tag: "tour-scheduler",
101
+ props: { phoneNumber: PHONE },
102
+ propsAfterMount: {
103
+ waitingForAvailabilities: false,
104
+ shouldAllowScheduleLoading: false,
105
+ shouldAllowScheduling: true,
106
+ },
107
+ french: SCHEDULE_TOUR_FR,
108
+ english: SCHEDULE_TOUR_EN,
109
+ ontarioCopy: `By providing your number and/or email address and clicking schedule tour, you consent to receive recurring commercial electronic messages and calls from or on behalf of ${ORG} at the provided number and/or email address. Messages may be AI or human generated.`,
110
+ newYorkCopy: `By providing your number and clicking schedule tour, you consent to receive recurring marketing text and voice messages and calls from or on behalf of ${ORG} at this number using artificial voice or an autodialer system.`,
111
+ newYorkLinks: [US_PRIVACY_LINK],
112
+ },
113
+ ];
114
+
115
+ const renderWidget = async (
116
+ widget: WidgetCase,
117
+ region: Region
118
+ ): Promise<Widget> => {
119
+ const element = document.createElement(widget.tag) as Widget;
120
+ Object.assign(element, widget.props, {
121
+ orgLegalName: ORG,
122
+ country: region.country,
123
+ buildingState: region.state,
124
+ });
125
+ document.body.appendChild(element);
126
+ Object.assign(element, widget.propsAfterMount);
127
+ await element.updateComplete;
128
+ return element;
129
+ };
130
+
131
+ afterEach(() => {
132
+ restore();
133
+ });
134
+
135
+ for (const widget of WIDGETS) {
136
+ it(`shows legal's French then English consent on the ${widget.name} only for Quebec`, async () => {
137
+ useFakeXMLHttpRequest();
138
+
139
+ for (const region of QUEBEC) {
140
+ const quebec = await renderWidget(widget, region);
141
+ expectQuebecConsent(quebec.shadowRoot, widget.french, widget.english);
142
+ quebec.remove();
143
+ }
144
+
145
+ const ontario = await renderWidget(widget, ONTARIO);
146
+ expectNoFrench(ontario.shadowRoot);
147
+ expect(renderedText(ontario.shadowRoot)).to.include(widget.ontarioCopy);
148
+ expect(linkHrefs(ontario.shadowRoot)).to.include(
149
+ "privacy policy -> https://eliseai.com/canada-policy"
150
+ );
151
+ ontario.remove();
152
+
153
+ const newYork = await renderWidget(widget, NEW_YORK);
154
+ expectNoFrench(newYork.shadowRoot);
155
+ expect(renderedText(newYork.shadowRoot)).to.include(widget.newYorkCopy);
156
+ expect(linkHrefs(newYork.shadowRoot)).to.include.members(
157
+ widget.newYorkLinks
158
+ );
159
+ newYork.remove();
160
+ });
161
+ }