@meetelise/chat 1.55.1 → 1.55.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.
- package/dist/src/WebComponent/FeeCalculator/utils/date-utils.d.ts +9 -0
- package/dist/src/globals.d.ts +0 -1
- package/package.json +1 -1
- package/public/dist/index.js +238 -237
- package/src/WebComponent/FeeCalculator/components/floor-plan-selector/floor-plan-selector.ts +16 -6
- package/src/WebComponent/FeeCalculator/fee-calculator.ts +2 -2
- package/src/WebComponent/FeeCalculator/utils/date-utils.ts +17 -0
- package/src/feeCalculatorMoveInDate.test.ts +113 -0
- package/src/globals.ts +0 -4
package/src/WebComponent/FeeCalculator/components/floor-plan-selector/floor-plan-selector.ts
CHANGED
|
@@ -1,10 +1,12 @@
|
|
|
1
|
+
import isBefore from "date-fns/isBefore";
|
|
2
|
+
import startOfToday from "date-fns/startOfToday";
|
|
1
3
|
import { LitElement, html, TemplateResult } from "lit";
|
|
2
4
|
import { customElement, property, state } from "lit/decorators.js";
|
|
3
5
|
import { LayoutOption } from "../../../../fetchBuildingWebchatView";
|
|
4
6
|
import { ALL_LAYOUTS_OPTION, DEFAULT_LEASE_TERM } from "../../constants";
|
|
5
7
|
import { Unit } from "../../../../services/fees/fetchBuildingUnits";
|
|
6
8
|
import floorPlanSelectorStyles from "./floor-plan-selector-styles";
|
|
7
|
-
import {
|
|
9
|
+
import { formatLocalDate, getTodayString } from "../../utils/date-utils";
|
|
8
10
|
|
|
9
11
|
import "../floorplan-image-card/floorplan-image-card";
|
|
10
12
|
|
|
@@ -46,7 +48,7 @@ export class FloorPlanSelector extends LitElement {
|
|
|
46
48
|
onLeaseTermChange: ((leaseTerm: number) => void) | null = null;
|
|
47
49
|
|
|
48
50
|
@state()
|
|
49
|
-
moveInDate: Date | null =
|
|
51
|
+
moveInDate: Date | null = startOfToday();
|
|
50
52
|
|
|
51
53
|
@state()
|
|
52
54
|
leaseTerm = DEFAULT_LEASE_TERM;
|
|
@@ -126,15 +128,20 @@ export class FloorPlanSelector extends LitElement {
|
|
|
126
128
|
handleMoveInDateChange = (e: Event): void => {
|
|
127
129
|
const inputElement = e.target as HTMLInputElement;
|
|
128
130
|
const inputValue = inputElement.value;
|
|
131
|
+
const today = startOfToday();
|
|
129
132
|
|
|
130
133
|
if (!inputValue) {
|
|
131
|
-
this.moveInDate =
|
|
134
|
+
this.moveInDate = today;
|
|
132
135
|
} else {
|
|
133
136
|
const [year, month, day] = inputValue.split("-").map(Number);
|
|
134
|
-
|
|
137
|
+
const moveInDate = new Date(year, month - 1, day);
|
|
138
|
+
// Typing skips `min`, and Chrome reports each digit of a typed year (0002, 0020...) as a date.
|
|
139
|
+
// Ignore past dates instead of resetting the input mid-typing.
|
|
140
|
+
if (isBefore(moveInDate, today)) return;
|
|
141
|
+
this.moveInDate = moveInDate;
|
|
135
142
|
}
|
|
136
143
|
|
|
137
|
-
this.onMoveInDateChange?.(inputValue ||
|
|
144
|
+
this.onMoveInDateChange?.(inputValue || formatLocalDate(today));
|
|
138
145
|
};
|
|
139
146
|
|
|
140
147
|
handleLeaseTermChange = (e: Event): void => {
|
|
@@ -225,7 +232,10 @@ export class FloorPlanSelector extends LitElement {
|
|
|
225
232
|
<input
|
|
226
233
|
type="date"
|
|
227
234
|
placeholder="Select move in date"
|
|
228
|
-
|
|
235
|
+
min=${getTodayString()}
|
|
236
|
+
.value=${this.moveInDate
|
|
237
|
+
? formatLocalDate(this.moveInDate)
|
|
238
|
+
: ""}
|
|
229
239
|
@input=${this.handleMoveInDateChange}
|
|
230
240
|
/>
|
|
231
241
|
</div>
|
|
@@ -18,7 +18,7 @@ import { XOutlineIcon } from "../icons/XOutlineIcon";
|
|
|
18
18
|
import { IncentiveV2 } from "../../types/incentive-v2";
|
|
19
19
|
import { Unit } from "../../services/fees/fetchBuildingUnits";
|
|
20
20
|
import { RentableItemSummary } from "./model/rentable-item-summary";
|
|
21
|
-
import {
|
|
21
|
+
import { getTodayString } from "./utils/date-utils";
|
|
22
22
|
import { MarketableFee, MarketableFeeFrequency } from "./model/marketable-fee";
|
|
23
23
|
import { ChargeInputs, QuoteError } from "../../services/fees/calculateQuote";
|
|
24
24
|
import { DesiredRentableItem } from "./model/desired-rentable-item";
|
|
@@ -96,7 +96,7 @@ export class FeeCalculator extends LitElement {
|
|
|
96
96
|
private desiredRentableItems: DesiredRentableItem[] = [];
|
|
97
97
|
|
|
98
98
|
@state()
|
|
99
|
-
private moveInDateString: string =
|
|
99
|
+
private moveInDateString: string = getTodayString();
|
|
100
100
|
|
|
101
101
|
@state()
|
|
102
102
|
private leaseTerm = DEFAULT_LEASE_TERM;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import format from "date-fns/format";
|
|
2
|
+
import startOfToday from "date-fns/startOfToday";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Formats a date as its local `yyyy-MM-dd` calendar date, the value format of a date input.
|
|
6
|
+
* `toISOString()` gives the UTC date instead, which can be a day off.
|
|
7
|
+
*/
|
|
8
|
+
export function formatLocalDate(date: Date): string {
|
|
9
|
+
return format(date, "yyyy-MM-dd");
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Gets the prospect's local date today as `yyyy-MM-dd`.
|
|
14
|
+
*/
|
|
15
|
+
export function getTodayString(): string {
|
|
16
|
+
return formatLocalDate(startOfToday());
|
|
17
|
+
}
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import { expect } from "@esm-bundle/chai";
|
|
2
|
+
import {
|
|
3
|
+
restore,
|
|
4
|
+
useFakeTimers,
|
|
5
|
+
useFakeXMLHttpRequest,
|
|
6
|
+
} from "sinon/pkg/sinon-esm";
|
|
7
|
+
import "../public/dist/index";
|
|
8
|
+
import type { FloorPlanSelector } from "./WebComponent/FeeCalculator/components/floor-plan-selector/floor-plan-selector";
|
|
9
|
+
import type { FeeCalculator } from "./WebComponent/FeeCalculator/fee-calculator";
|
|
10
|
+
import { formatLocalDate } from "./WebComponent/FeeCalculator/utils/date-utils";
|
|
11
|
+
|
|
12
|
+
type FakeRequest = XMLHttpRequest & { url: string };
|
|
13
|
+
|
|
14
|
+
// Late evening, and far from the real date, so a date read at module load cannot match.
|
|
15
|
+
const NOW = new Date(2030, 4, 20, 23, 30);
|
|
16
|
+
const TODAY = "2030-05-20";
|
|
17
|
+
|
|
18
|
+
const waitFor = async <T>(
|
|
19
|
+
find: () => T | undefined,
|
|
20
|
+
description: string
|
|
21
|
+
): Promise<T> => {
|
|
22
|
+
for (let attempt = 0; attempt < 100; attempt++) {
|
|
23
|
+
const found = find();
|
|
24
|
+
if (found) return found;
|
|
25
|
+
await new Promise((resolve) => setTimeout(resolve, 10));
|
|
26
|
+
}
|
|
27
|
+
throw new Error(`Timed out waiting for ${description}`);
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
beforeEach(() => {
|
|
31
|
+
useFakeTimers({ now: NOW, toFake: ["Date"] });
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
afterEach(() => {
|
|
35
|
+
restore();
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
it("formats the local calendar date at both ends of the day", () => {
|
|
39
|
+
expect(formatLocalDate(new Date(2030, 4, 20, 0, 0))).to.equal(TODAY);
|
|
40
|
+
expect(formatLocalDate(new Date(2030, 4, 20, 23, 59))).to.equal(TODAY);
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
describe("floor-plan-selector move-in date", () => {
|
|
44
|
+
let selector: FloorPlanSelector;
|
|
45
|
+
let sentDates: string[];
|
|
46
|
+
|
|
47
|
+
const getDateInput = (): HTMLInputElement => {
|
|
48
|
+
const input =
|
|
49
|
+
selector.shadowRoot?.querySelector<HTMLInputElement>(
|
|
50
|
+
'input[type="date"]'
|
|
51
|
+
);
|
|
52
|
+
if (!input) throw new Error("Expected move-in date input");
|
|
53
|
+
return input;
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
const enterDate = async (value: string): Promise<void> => {
|
|
57
|
+
const input = getDateInput();
|
|
58
|
+
input.value = value;
|
|
59
|
+
input.dispatchEvent(new Event("input"));
|
|
60
|
+
await selector.updateComplete;
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
beforeEach(async () => {
|
|
64
|
+
sentDates = [];
|
|
65
|
+
selector = document.createElement(
|
|
66
|
+
"floor-plan-selector"
|
|
67
|
+
) as FloorPlanSelector;
|
|
68
|
+
selector.onMoveInDateChange = (date) => sentDates.push(date);
|
|
69
|
+
document.body.appendChild(selector);
|
|
70
|
+
await selector.updateComplete;
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
afterEach(() => {
|
|
74
|
+
selector.remove();
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
it("starts on today and does not allow earlier dates", () => {
|
|
78
|
+
expect(getDateInput().value).to.equal(TODAY);
|
|
79
|
+
expect(getDateInput().min).to.equal(TODAY);
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
it("shows and sends the date the prospect picks", async () => {
|
|
83
|
+
await enterDate("2030-06-01");
|
|
84
|
+
|
|
85
|
+
expect(getDateInput().value).to.equal("2030-06-01");
|
|
86
|
+
expect(sentDates).to.deep.equal(["2030-06-01"]);
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
it("ignores typed dates before today", async () => {
|
|
90
|
+
// Chrome reports each digit of a typed year as a date, for example 0002-06-01.
|
|
91
|
+
await enterDate("2030-05-19");
|
|
92
|
+
await enterDate("0002-06-01");
|
|
93
|
+
|
|
94
|
+
expect(sentDates).to.deep.equal([]);
|
|
95
|
+
});
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
it("requests units for today's local date by default", async () => {
|
|
99
|
+
const fakeXhr = useFakeXMLHttpRequest();
|
|
100
|
+
const requests: FakeRequest[] = [];
|
|
101
|
+
fakeXhr.onCreate = (request: FakeRequest) => requests.push(request);
|
|
102
|
+
const calculator = document.createElement("fee-calculator") as FeeCalculator;
|
|
103
|
+
calculator.buildingSlug = "test-building";
|
|
104
|
+
document.body.appendChild(calculator);
|
|
105
|
+
|
|
106
|
+
const unitsRequest = await waitFor(
|
|
107
|
+
() => requests.find((request) => request.url.includes("/units")),
|
|
108
|
+
"the units request"
|
|
109
|
+
);
|
|
110
|
+
|
|
111
|
+
expect(unitsRequest.url).to.include(`move_in_date_earliest=${TODAY}`);
|
|
112
|
+
calculator.remove();
|
|
113
|
+
});
|