@meetelise/chat 1.51.12 → 1.51.14
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.
- package/dist/src/insertLeadSourceIntoSchedulerLinks.d.ts +1 -1
- package/package.json +1 -1
- package/public/dist/index.js +34 -34
- package/src/WebComponent/me-chat.ts +6 -1
- package/src/__mocks__/analytics.ts +7 -0
- package/src/insertDNIIntoWebsite.test.ts +217 -0
- package/src/insertDNIIntoWebsite.ts +1 -1
- package/src/insertLeadSourceIntoSchedulerLinks.test.ts +216 -0
- package/src/insertLeadSourceIntoSchedulerLinks.ts +31 -2
|
@@ -424,7 +424,12 @@ export class MEChat extends LitElement {
|
|
|
424
424
|
this.enabledChatWidgets.sstMobile ||
|
|
425
425
|
this.orgSlug === "radco"
|
|
426
426
|
) {
|
|
427
|
-
replaceSSTLinkWithTourWidgetTrigger(
|
|
427
|
+
replaceSSTLinkWithTourWidgetTrigger(
|
|
428
|
+
this.handleTourClicked,
|
|
429
|
+
this.orgSlug,
|
|
430
|
+
this.buildingSlug,
|
|
431
|
+
() => this.isConnected
|
|
432
|
+
);
|
|
428
433
|
if (featureFlagReplaceScheduleTourCtaWebsite) {
|
|
429
434
|
replacePageRedirectElementWithTourWidgetTrigger(this.handleTourClicked);
|
|
430
435
|
}
|
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
import { expect } from "@esm-bundle/chai";
|
|
2
|
+
import { insertDNIIntoWebsite } from "./insertDNIIntoWebsite";
|
|
3
|
+
|
|
4
|
+
const DNI_NUMBER = "3035550123";
|
|
5
|
+
const DNI_DISPLAY = "(303) 555-0123";
|
|
6
|
+
|
|
7
|
+
const render = (html: string): void => {
|
|
8
|
+
document.body.innerHTML = html;
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
const insert = (cleanPhoneNumber: string = DNI_NUMBER): number =>
|
|
12
|
+
insertDNIIntoWebsite(cleanPhoneNumber, "test-org", "test-building");
|
|
13
|
+
|
|
14
|
+
const textOf = (selector: string): string | null =>
|
|
15
|
+
document.querySelector(selector)?.textContent ?? null;
|
|
16
|
+
|
|
17
|
+
const hrefOf = (selector: string): string | null =>
|
|
18
|
+
document.querySelector(selector)?.getAttribute("href") ?? null;
|
|
19
|
+
|
|
20
|
+
describe("insertDNIIntoWebsite", () => {
|
|
21
|
+
afterEach(() => {
|
|
22
|
+
document.body.innerHTML = "";
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
describe("display text", () => {
|
|
26
|
+
it("replaces common US phone formats", () => {
|
|
27
|
+
const formats = [
|
|
28
|
+
"(855) 383-2298",
|
|
29
|
+
"855-383-2298",
|
|
30
|
+
"855.383.2298",
|
|
31
|
+
"855 383 2298",
|
|
32
|
+
"(855)383-2298",
|
|
33
|
+
"855-3832298",
|
|
34
|
+
];
|
|
35
|
+
render(formats.map((f, i) => `<span id="n${i}">${f}</span>`).join(""));
|
|
36
|
+
|
|
37
|
+
insert();
|
|
38
|
+
|
|
39
|
+
formats.forEach((format, i) => {
|
|
40
|
+
expect(textOf(`#n${i}`)).to.equal(DNI_DISPLAY, format);
|
|
41
|
+
});
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
it("keeps a +1 country code prefix", () => {
|
|
45
|
+
render(`<span id="n">+1 855-383-2298</span>`);
|
|
46
|
+
|
|
47
|
+
insert();
|
|
48
|
+
|
|
49
|
+
expect(textOf("#n")).to.equal(`+1 ${DNI_DISPLAY}`);
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
it("replaces a number embedded in surrounding text", () => {
|
|
53
|
+
render(`<p id="n">Call us at 855-383-2298 today!</p>`);
|
|
54
|
+
|
|
55
|
+
insert();
|
|
56
|
+
|
|
57
|
+
expect(textOf("#n")).to.equal(`Call us at ${DNI_DISPLAY} today!`);
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
it("accepts 10-digit, 11-digit, and +1 DNI numbers", () => {
|
|
61
|
+
for (const dni of ["3035550123", "13035550123", "+13035550123"]) {
|
|
62
|
+
render(`<span id="n">855-383-2298</span>`);
|
|
63
|
+
|
|
64
|
+
insert(dni);
|
|
65
|
+
|
|
66
|
+
expect(textOf("#n")).to.equal(DNI_DISPLAY, dni);
|
|
67
|
+
}
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
it("replaces both the text and tel: href of a phone link", () => {
|
|
71
|
+
render(`<a id="n" href="tel:8553832298">(855) 383-2298</a>`);
|
|
72
|
+
|
|
73
|
+
insert();
|
|
74
|
+
|
|
75
|
+
expect(textOf("#n")).to.equal(DNI_DISPLAY);
|
|
76
|
+
expect(hrefOf("#n")).to.equal(`tel:${DNI_NUMBER}`);
|
|
77
|
+
});
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
describe("NANP validity", () => {
|
|
81
|
+
it("ignores numbers whose area code starts with 0 or 1", () => {
|
|
82
|
+
render(
|
|
83
|
+
`<span id="a">123-456-7890</span><span id="b">(023) 456-7890</span>`
|
|
84
|
+
);
|
|
85
|
+
|
|
86
|
+
insert();
|
|
87
|
+
|
|
88
|
+
expect(textOf("#a")).to.equal("123-456-7890");
|
|
89
|
+
expect(textOf("#b")).to.equal("(023) 456-7890");
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
it("ignores numbers whose exchange starts with 0 or 1", () => {
|
|
93
|
+
render(
|
|
94
|
+
`<span id="a">(212) 155-1234</span><span id="b">212-055-1234</span>`
|
|
95
|
+
);
|
|
96
|
+
|
|
97
|
+
insert();
|
|
98
|
+
|
|
99
|
+
expect(textOf("#a")).to.equal("(212) 155-1234");
|
|
100
|
+
expect(textOf("#b")).to.equal("212-055-1234");
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
it("ignores western US longitudes in visible text", () => {
|
|
104
|
+
render(`<p id="n">Lat/Long: 39.9038078, -104.9932859</p>`);
|
|
105
|
+
|
|
106
|
+
insert();
|
|
107
|
+
|
|
108
|
+
expect(textOf("#n")).to.equal("Lat/Long: 39.9038078, -104.9932859");
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
it("keeps JSON-LD geo coordinates valid", () => {
|
|
112
|
+
render(
|
|
113
|
+
`<script id="n" type="application/ld+json">${JSON.stringify({
|
|
114
|
+
"@type": "ApartmentComplex",
|
|
115
|
+
telephone: "855-383-2298",
|
|
116
|
+
geo: {
|
|
117
|
+
"@type": "GeoCoordinates",
|
|
118
|
+
latitude: 39.9038078,
|
|
119
|
+
longitude: -104.9932859,
|
|
120
|
+
},
|
|
121
|
+
})}</script>`
|
|
122
|
+
);
|
|
123
|
+
|
|
124
|
+
insert();
|
|
125
|
+
|
|
126
|
+
const schema = JSON.parse(textOf("#n") ?? "");
|
|
127
|
+
expect(schema.geo.latitude).to.equal(39.9038078);
|
|
128
|
+
expect(schema.geo.longitude).to.equal(-104.9932859);
|
|
129
|
+
expect(schema.telephone).to.equal(DNI_DISPLAY);
|
|
130
|
+
});
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
describe("guardrails", () => {
|
|
134
|
+
it("does not replace protected numbers", () => {
|
|
135
|
+
render(`<a id="n" href="tel:8002221222">800-222-1222</a>`);
|
|
136
|
+
|
|
137
|
+
insert();
|
|
138
|
+
|
|
139
|
+
expect(textOf("#n")).to.equal("800-222-1222");
|
|
140
|
+
expect(hrefOf("#n")).to.equal("tel:8002221222");
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
it("does not replace numbers next to emergency text", () => {
|
|
144
|
+
render(
|
|
145
|
+
`<div><strong>After-hours emergency</strong><span id="en">855-383-2298</span></div>` +
|
|
146
|
+
`<div><strong>Mantenimiento de emergencia</strong><span id="es">855-383-2298</span></div>`
|
|
147
|
+
);
|
|
148
|
+
|
|
149
|
+
insert();
|
|
150
|
+
|
|
151
|
+
expect(textOf("#en")).to.equal("855-383-2298");
|
|
152
|
+
expect(textOf("#es")).to.equal("855-383-2298");
|
|
153
|
+
});
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
describe("hrefs", () => {
|
|
157
|
+
it("rewrites tel: hrefs in any format", () => {
|
|
158
|
+
const hrefs = [
|
|
159
|
+
"tel:8553832298",
|
|
160
|
+
"tel:18553832298",
|
|
161
|
+
"tel:+18553832298",
|
|
162
|
+
"tel:855-383-2298",
|
|
163
|
+
"tel:(855) 383-2298",
|
|
164
|
+
];
|
|
165
|
+
render(
|
|
166
|
+
hrefs.map((h, i) => `<a id="h${i}" href="${h}">Call</a>`).join("")
|
|
167
|
+
);
|
|
168
|
+
|
|
169
|
+
insert();
|
|
170
|
+
|
|
171
|
+
hrefs.forEach((href, i) => {
|
|
172
|
+
expect(hrefOf(`#h${i}`)).to.equal(`tel:${DNI_NUMBER}`, href);
|
|
173
|
+
});
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
it("rewrites sms, whatsapp, and facetime hrefs", () => {
|
|
177
|
+
render(
|
|
178
|
+
`<a id="sms" href="sms:8553832298">Text</a>` +
|
|
179
|
+
`<a id="wa" href="whatsapp://send?phone=18553832298">WhatsApp</a>` +
|
|
180
|
+
`<a id="ft" href="facetime:8553832298">FaceTime</a>` +
|
|
181
|
+
`<a id="fta" href="facetime-audio:8553832298">FaceTime Audio</a>`
|
|
182
|
+
);
|
|
183
|
+
|
|
184
|
+
insert();
|
|
185
|
+
|
|
186
|
+
expect(hrefOf("#sms")).to.equal(`sms:${DNI_NUMBER}`);
|
|
187
|
+
expect(hrefOf("#wa")).to.equal(`whatsapp://send?phone=${DNI_NUMBER}`);
|
|
188
|
+
expect(hrefOf("#ft")).to.equal(`facetime:${DNI_NUMBER}`);
|
|
189
|
+
expect(hrefOf("#fta")).to.equal(`facetime-audio:${DNI_NUMBER}`);
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
it("leaves non-phone hrefs untouched", () => {
|
|
193
|
+
render(`<a id="n" href="/floor-plans">Floor plans</a>`);
|
|
194
|
+
|
|
195
|
+
insert();
|
|
196
|
+
|
|
197
|
+
expect(hrefOf("#n")).to.equal("/floor-plans");
|
|
198
|
+
});
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
describe("return value", () => {
|
|
202
|
+
it("counts elements whose display text changed", () => {
|
|
203
|
+
render(
|
|
204
|
+
`<span>855-383-2298</span><span>(855) 383-2298</span>` +
|
|
205
|
+
`<a href="tel:8553832298">Call</a><span>123-456-7890</span>`
|
|
206
|
+
);
|
|
207
|
+
|
|
208
|
+
expect(insert()).to.equal(2);
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
it("returns 0 when no phone number is found", () => {
|
|
212
|
+
render(`<p>No phone numbers here, just 39.9038078, -104.9932859.</p>`);
|
|
213
|
+
|
|
214
|
+
expect(insert()).to.equal(0);
|
|
215
|
+
});
|
|
216
|
+
});
|
|
217
|
+
});
|
|
@@ -31,7 +31,7 @@ export const insertDNIIntoWebsite = (
|
|
|
31
31
|
const newFacetimeAudioNumber = `facetime-audio:${cleanedNumber}`;
|
|
32
32
|
|
|
33
33
|
const phoneNumberPattern =
|
|
34
|
-
/(?:\+1[\s.-]?)?(?:\(\d{
|
|
34
|
+
/(?:\+1[\s.-]?)?(?:\([2-9]\d{2}\)[\s.-]?|[2-9]\d{2}[\s.-])[2-9]\d{2}[\s.-]?\d{4}/g;
|
|
35
35
|
const hrefNumberPattern =
|
|
36
36
|
/(tel|sms):(?:\+1\s)?(?:(?:\(\d{3}\)\s|\d{3}-)\d{3}-\d{4}|\d{10,11})/g;
|
|
37
37
|
|
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
import { expect } from "@esm-bundle/chai";
|
|
2
|
+
import {
|
|
3
|
+
replacePageRedirectElementWithTourWidgetTrigger,
|
|
4
|
+
replaceSSTLinkWithTourWidgetTrigger,
|
|
5
|
+
replaceTourTriggerElementWithTourWidgetTrigger,
|
|
6
|
+
} from "./insertLeadSourceIntoSchedulerLinks";
|
|
7
|
+
|
|
8
|
+
const APP = "https://app.meetelise.com";
|
|
9
|
+
const ORG = "test-org";
|
|
10
|
+
const BUILDING = "test-building";
|
|
11
|
+
|
|
12
|
+
const render = (hrefs: string[]): HTMLAnchorElement[] => {
|
|
13
|
+
document.body.innerHTML = hrefs
|
|
14
|
+
.map((href) => `<a href="${href}">link</a>`)
|
|
15
|
+
.join("");
|
|
16
|
+
return Array.from(document.querySelectorAll("a"));
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
// Returns whether the widget cancelled the click. The document listener runs
|
|
20
|
+
// after the link's own listeners, then cancels navigation so the test page stays.
|
|
21
|
+
const click = (element: Element): boolean => {
|
|
22
|
+
let defaultPrevented = false;
|
|
23
|
+
const recordAndCancel = (event: Event) => {
|
|
24
|
+
defaultPrevented = event.defaultPrevented;
|
|
25
|
+
event.preventDefault();
|
|
26
|
+
};
|
|
27
|
+
document.addEventListener("click", recordAndCancel);
|
|
28
|
+
element.dispatchEvent(
|
|
29
|
+
new MouseEvent("click", { bubbles: true, cancelable: true })
|
|
30
|
+
);
|
|
31
|
+
document.removeEventListener("click", recordAndCancel);
|
|
32
|
+
return defaultPrevented;
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
const clickAll = (links: HTMLAnchorElement[]): Record<string, boolean> =>
|
|
36
|
+
Object.fromEntries(
|
|
37
|
+
links.map((link) => [link.getAttribute("href") ?? "", click(link)])
|
|
38
|
+
);
|
|
39
|
+
|
|
40
|
+
const allEqual = (hrefs: string[], value: boolean): Record<string, boolean> =>
|
|
41
|
+
Object.fromEntries(hrefs.map((href) => [href, value]));
|
|
42
|
+
|
|
43
|
+
describe("replaceSSTLinkWithTourWidgetTrigger", () => {
|
|
44
|
+
let opened: MouseEvent[];
|
|
45
|
+
const install = (orgSlug = ORG, buildingSlug = BUILDING): void =>
|
|
46
|
+
replaceSSTLinkWithTourWidgetTrigger(
|
|
47
|
+
(event) => opened.push(event),
|
|
48
|
+
orgSlug,
|
|
49
|
+
buildingSlug,
|
|
50
|
+
() => true
|
|
51
|
+
);
|
|
52
|
+
|
|
53
|
+
beforeEach(() => {
|
|
54
|
+
opened = [];
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
afterEach(() => {
|
|
58
|
+
document.body.innerHTML = "";
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
it("opens the scheduler only for the current building's tour link on a portfolio page", () => {
|
|
62
|
+
const [ownTour, otherTour, contactUs] = render([
|
|
63
|
+
`${APP}/${ORG}/${BUILDING}/tour`,
|
|
64
|
+
`${APP}/${ORG}/other-building/tour`,
|
|
65
|
+
`${APP}/${ORG}/${BUILDING}/contactUs`,
|
|
66
|
+
]);
|
|
67
|
+
|
|
68
|
+
install();
|
|
69
|
+
|
|
70
|
+
expect({
|
|
71
|
+
ownTour: click(ownTour),
|
|
72
|
+
otherTour: click(otherTour),
|
|
73
|
+
contactUs: click(contactUs),
|
|
74
|
+
}).to.deep.equal({ ownTour: true, otherTour: false, contactUs: false });
|
|
75
|
+
expect(opened).to.have.length(1);
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
it("matches the current building's tour link with query strings, fragments, trailing slashes and any casing", () => {
|
|
79
|
+
const hrefs = [
|
|
80
|
+
`${APP}/${ORG}/${BUILDING}/tour/`,
|
|
81
|
+
`${APP}/${ORG}/${BUILDING}/tour?utm_source=website`,
|
|
82
|
+
`${APP}/${ORG}/${BUILDING}/tour/?hideScrollBars=true`,
|
|
83
|
+
`${APP}/${ORG}/${BUILDING}/tour?stateId=abc123`,
|
|
84
|
+
`${APP}/${ORG}/${BUILDING}/tour#schedule`,
|
|
85
|
+
`${APP}/${ORG}//${BUILDING}/tour`,
|
|
86
|
+
`${APP}/Test-Org/Test-Building/Tour`,
|
|
87
|
+
];
|
|
88
|
+
const links = render(hrefs);
|
|
89
|
+
|
|
90
|
+
install();
|
|
91
|
+
|
|
92
|
+
expect(clickAll(links)).to.deep.equal(allEqual(hrefs, true));
|
|
93
|
+
expect(opened).to.have.length(hrefs.length);
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
it("matches when the widget was started with differently cased slugs", () => {
|
|
97
|
+
const [link] = render([`${APP}/${ORG}/${BUILDING}/tour`]);
|
|
98
|
+
|
|
99
|
+
install("Test-Org", "TEST-BUILDING");
|
|
100
|
+
|
|
101
|
+
expect(click(link)).to.equal(true);
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
it("keeps default navigation for every other app.meetelise.com link", () => {
|
|
105
|
+
const hrefs = [
|
|
106
|
+
`${APP}/other-org/${BUILDING}/tour`,
|
|
107
|
+
`${APP}/${ORG}/${BUILDING}-2/tour`,
|
|
108
|
+
`${APP}/${ORG}/${BUILDING}/tour-2`,
|
|
109
|
+
`${APP}/${ORG}/${BUILDING}/tour/extra`,
|
|
110
|
+
`${APP}/${ORG}/${BUILDING}/contactUs?utm_source=website`,
|
|
111
|
+
`${APP}/${ORG}/${BUILDING}`,
|
|
112
|
+
`${APP}/${ORG}/tour`,
|
|
113
|
+
`${APP}/${BUILDING}/tour`,
|
|
114
|
+
`${APP}/settings/widgets`,
|
|
115
|
+
`${APP}/`,
|
|
116
|
+
];
|
|
117
|
+
const links = render(hrefs);
|
|
118
|
+
|
|
119
|
+
install();
|
|
120
|
+
|
|
121
|
+
expect(clickAll(links)).to.deep.equal(allEqual(hrefs, false));
|
|
122
|
+
expect(opened).to.have.length(0);
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
it("never intercepts links outside https://app.meetelise.com", () => {
|
|
126
|
+
const hrefs = [
|
|
127
|
+
`/${ORG}/${BUILDING}/tour`,
|
|
128
|
+
`http://app.meetelise.com/${ORG}/${BUILDING}/tour`,
|
|
129
|
+
`https://www.example.com/${ORG}/${BUILDING}/tour`,
|
|
130
|
+
];
|
|
131
|
+
const links = render(hrefs);
|
|
132
|
+
|
|
133
|
+
install();
|
|
134
|
+
|
|
135
|
+
expect(clickAll(links)).to.deep.equal(allEqual(hrefs, false));
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
it("lets a removed widget's tour link navigate after the widget switches buildings", () => {
|
|
139
|
+
const [firstTour, secondTour] = render([
|
|
140
|
+
`${APP}/${ORG}/${BUILDING}/tour`,
|
|
141
|
+
`${APP}/${ORG}/other-building/tour`,
|
|
142
|
+
]);
|
|
143
|
+
const openedByFirst: MouseEvent[] = [];
|
|
144
|
+
const openedBySecond: MouseEvent[] = [];
|
|
145
|
+
let firstMounted = true;
|
|
146
|
+
replaceSSTLinkWithTourWidgetTrigger(
|
|
147
|
+
(event) => openedByFirst.push(event),
|
|
148
|
+
ORG,
|
|
149
|
+
BUILDING,
|
|
150
|
+
() => firstMounted
|
|
151
|
+
);
|
|
152
|
+
|
|
153
|
+
// Studio removes the first widget and starts one for another building.
|
|
154
|
+
firstMounted = false;
|
|
155
|
+
replaceSSTLinkWithTourWidgetTrigger(
|
|
156
|
+
(event) => openedBySecond.push(event),
|
|
157
|
+
ORG,
|
|
158
|
+
"other-building",
|
|
159
|
+
() => true
|
|
160
|
+
);
|
|
161
|
+
|
|
162
|
+
expect({
|
|
163
|
+
firstTour: click(firstTour),
|
|
164
|
+
secondTour: click(secondTour),
|
|
165
|
+
}).to.deep.equal({ firstTour: false, secondTour: true });
|
|
166
|
+
expect(openedByFirst).to.have.length(0);
|
|
167
|
+
expect(openedBySecond).to.have.length(1);
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
it("intercepts nothing when the building slug is missing", () => {
|
|
171
|
+
const hrefs = [`${APP}/${ORG}/${BUILDING}/tour`, `${APP}/${ORG}//tour`];
|
|
172
|
+
const links = render(hrefs);
|
|
173
|
+
|
|
174
|
+
install(ORG, "");
|
|
175
|
+
|
|
176
|
+
expect(clickAll(links)).to.deep.equal(allEqual(hrefs, false));
|
|
177
|
+
expect(opened).to.have.length(0);
|
|
178
|
+
});
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
describe("site schedule-a-tour hooks", () => {
|
|
182
|
+
let opened: MouseEvent[];
|
|
183
|
+
|
|
184
|
+
beforeEach(() => {
|
|
185
|
+
opened = [];
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
afterEach(() => {
|
|
189
|
+
document.body.innerHTML = "";
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
it("opens the scheduler from links ending in schedule-a-tour", () => {
|
|
193
|
+
const [link] = render(["/apartments/oh/westerville/schedule-a-tour"]);
|
|
194
|
+
|
|
195
|
+
replacePageRedirectElementWithTourWidgetTrigger((event) =>
|
|
196
|
+
opened.push(event)
|
|
197
|
+
);
|
|
198
|
+
|
|
199
|
+
expect(click(link)).to.equal(true);
|
|
200
|
+
expect(opened).to.have.length(1);
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
it("replaces #leaseleads-tour-open links with a scheduler trigger", () => {
|
|
204
|
+
render(["#leaseleads-tour-open"]);
|
|
205
|
+
|
|
206
|
+
replaceTourTriggerElementWithTourWidgetTrigger((event) =>
|
|
207
|
+
opened.push(event)
|
|
208
|
+
);
|
|
209
|
+
|
|
210
|
+
const trigger = document.querySelector("a");
|
|
211
|
+
expect(trigger?.hasAttribute("href")).to.equal(false);
|
|
212
|
+
expect(trigger?.textContent).to.equal("link");
|
|
213
|
+
expect(click(trigger as HTMLAnchorElement)).to.equal(true);
|
|
214
|
+
expect(opened).to.have.length(1);
|
|
215
|
+
});
|
|
216
|
+
});
|
|
@@ -18,14 +18,43 @@ export const insertLeadSourceIntoSchedulerLinks = (
|
|
|
18
18
|
});
|
|
19
19
|
};
|
|
20
20
|
|
|
21
|
+
// Mirrors the web-app's /:organization/:building/tour route, which ignores
|
|
22
|
+
// empty path segments. Case-insensitive so slug casing differences still match.
|
|
23
|
+
const isBuildingTourLink = (
|
|
24
|
+
href: string,
|
|
25
|
+
orgSlug: string,
|
|
26
|
+
buildingSlug: string
|
|
27
|
+
): boolean => {
|
|
28
|
+
const [org, building, page, ...rest] = new URL(href).pathname
|
|
29
|
+
.split("/")
|
|
30
|
+
.filter(Boolean)
|
|
31
|
+
.map((segment) => segment.toLowerCase());
|
|
32
|
+
return (
|
|
33
|
+
rest.length === 0 &&
|
|
34
|
+
page === "tour" &&
|
|
35
|
+
org === orgSlug.toLowerCase() &&
|
|
36
|
+
building === buildingSlug.toLowerCase()
|
|
37
|
+
);
|
|
38
|
+
};
|
|
39
|
+
|
|
21
40
|
export const replaceSSTLinkWithTourWidgetTrigger = (
|
|
22
|
-
handleTourClicked: (e: MouseEvent) => void
|
|
41
|
+
handleTourClicked: (e: MouseEvent) => void,
|
|
42
|
+
orgSlug: string,
|
|
43
|
+
buildingSlug: string,
|
|
44
|
+
isWidgetMounted: () => boolean
|
|
23
45
|
): void => {
|
|
24
46
|
const sstRedirectElements = document.querySelectorAll("a");
|
|
25
47
|
sstRedirectElements.forEach(function (element) {
|
|
26
48
|
const href = element.getAttribute("href");
|
|
27
|
-
if (
|
|
49
|
+
if (
|
|
50
|
+
href &&
|
|
51
|
+
href.startsWith("https://app.meetelise.com/") &&
|
|
52
|
+
isBuildingTourLink(href, orgSlug, buildingSlug)
|
|
53
|
+
) {
|
|
28
54
|
element.addEventListener("click", (event) => {
|
|
55
|
+
// Studio can swap the widget for another building's. A removed
|
|
56
|
+
// widget's listener must let the link navigate, not swallow the click.
|
|
57
|
+
if (!isWidgetMounted()) return;
|
|
29
58
|
event.preventDefault(); // Prevents the default href redirect
|
|
30
59
|
handleTourClicked(event as MouseEvent);
|
|
31
60
|
});
|