Skip to content

Commit f8e9cbd

Browse files
authored
fix(framework): wait cldr to be loaded (#11643)
Prevent language-aware components from re-rendering during a language change while language-specific data (e.g., CLDR, language bundles) is still loading and incomplete. Components that depend on this data, and attempting to render them prematurely can lead to runtime issues such as missing content, console errors, or exceptions. To avoid these problems, re-rendering is deferred until the language-specific data has fully loaded. Once the loading phase is complete, the language change will automatically trigger the necessary re-rendering. Fixes: #11601
1 parent b084a1f commit f8e9cbd

File tree

5 files changed

+83
-10
lines changed

5 files changed

+83
-10
lines changed

packages/base/src/UI5Element.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ import { getI18nBundle } from "./i18nBundle.js";
4343
import type I18nBundle from "./i18nBundle.js";
4444
import { fetchCldr } from "./asset-registries/LocaleData.js";
4545
import getLocale from "./locale/getLocale.js";
46+
import { getLanguageChangePending } from "./config/Language.js";
4647

4748
const DEV_MODE = true;
4849
let autoId = 0;
@@ -111,6 +112,15 @@ function _invalidate(this: UI5Element, changeInfo: ChangeInfo) {
111112
return;
112113
}
113114

115+
const ctor = this.constructor as typeof UI5Element;
116+
117+
// Skip re-rendering of language-aware components while language-specific data (e.g., CLDR, language bundles) is still loading.
118+
// Once all necessary language data has been loaded, the language change
119+
// will trigger a re-render of all language-aware components.
120+
if (ctor.getMetadata().isLanguageAware() && getLanguageChangePending()) {
121+
return;
122+
}
123+
114124
// Call the onInvalidation hook
115125
this.onInvalidation(changeInfo);
116126

packages/base/src/config/Language.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,17 @@ attachConfigurationReset(() => {
1616
fetchDefaultLanguage = undefined;
1717
});
1818

19+
// Flag indicating that a language change is in progress and not yet complete.
20+
// While this flag is true, language-aware components will not re-render.
21+
// These components may rely on language-specific data (e.g., CLDR, language bundles),
22+
// which might be unavailable during the loading phase.
23+
// During this phase, all re-rendering is postponed.
24+
// Once all necessary language data has been loaded, the language change
25+
// will trigger a re-render of all language-aware components.
26+
let languageChangePending = false;
27+
28+
const getLanguageChangePending = () => languageChangePending;
29+
1930
/**
2031
* Returns the currently configured language, or the browser language as a fallback.
2132
* @public
@@ -41,9 +52,13 @@ const setLanguage = async (language: string): Promise<void> => {
4152
return;
4253
}
4354

55+
languageChangePending = true;
4456
curLanguage = language;
4557

4658
await fireLanguageChange(language);
59+
60+
languageChangePending = false;
61+
4762
if (isBooted()) {
4863
await reRenderAllUI5Elements({ languageAware: true });
4964
}
@@ -92,4 +107,5 @@ export {
92107
getDefaultLanguage,
93108
setFetchDefaultLanguage,
94109
getFetchDefaultLanguage,
110+
getLanguageChangePending,
95111
};

packages/main/cypress/specs/DatePicker.cy.tsx

Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,6 @@ import DatePicker from "../../src/DatePicker.js";
55
import Label from "../../src/Label.js";
66

77
describe("Date Picker Tests", () => {
8-
afterEach(() => {
9-
// eslint-disable-next-line
10-
cy.wait(200);
11-
});
12-
138
it("input renders", () => {
149
cy.mount(<DatePicker></DatePicker>);
1510

@@ -1547,11 +1542,6 @@ describe("Date Picker Tests", () => {
15471542
});
15481543

15491544
describe("Legacy date customization and Islamic calendar type", () => {
1550-
afterEach(() => {
1551-
// eslint-disable-next-line
1552-
cy.wait(200);
1553-
});
1554-
15551545
const configurationObject = {
15561546
"formatSettings": {
15571547
"legacyDateCalendarCustomizing": [

packages/main/cypress/specs/DateRangePicker.cy.tsx

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -242,6 +242,8 @@ describe("DateRangePicker general interaction", () => {
242242
.should("be.focused");
243243

244244
cy.realPress("End")
245+
// TODO: Carret position need to be checked somehow before triggering next action
246+
cy.wait(100);
245247

246248
cy.realPress("PageDown");
247249

@@ -254,6 +256,8 @@ describe("DateRangePicker general interaction", () => {
254256
.should("have.attr", "value", "Jul 16, 2020 @ Jul 29, 2020");
255257

256258
cy.realPress("Home");
259+
// TODO: Carret position need to be checked somehow before triggering next action
260+
cy.wait(100);
257261

258262
cy.realPress("PageDown");
259263

@@ -585,6 +589,9 @@ describe("DateRangePicker general interaction", () => {
585589

586590
cy.mount(<DateRangePickerTemplate formatPattern="MM.yyyy" />);
587591

592+
// TODO: Remove when focus is applied on month, day, year picker in their onAfterRendering method. It takes the focus one they are rendered even if not visible
593+
cy.wait(500);
594+
588595
cy.get<DateRangePicker>("[ui5-daterange-picker]")
589596
.as("dateRangePicker")
590597
.shadow()

packages/main/cypress/support/commands/DatePicker.commands.ts

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,12 @@
1+
import type ResponsivePopover from "../../../src/ResponsivePopover.js";
2+
3+
const isPickerOpen = (open = false) => {
4+
cy.get("@datePicker")
5+
.shadow()
6+
.find<ResponsivePopover>("[ui5-responsive-popover].ui5-date-picker-popover")
7+
[open ? "ui5ResponsivePopoverOpened" : "ui5ResponsivePopoverClosed"]();
8+
};
9+
110
Cypress.Commands.add("ui5DatePickerGetInnerInput", { prevSubject: true }, subject => {
211
cy.wrap(subject)
312
.as("datePicker");
@@ -19,6 +28,8 @@ Cypress.Commands.add("ui5DatePickerGetPopoverDate", { prevSubject: true }, (subj
1928
cy.get("@datePicker")
2029
.should("have.attr", "open");
2130

31+
isPickerOpen(true);
32+
2233
cy.get("@datePicker")
2334
.shadow()
2435
.find("ui5-calendar")
@@ -40,6 +51,8 @@ Cypress.Commands.add("ui5DatePickerGetDisplayedDay", { prevSubject: true }, (sub
4051
cy.get("@datePicker")
4152
.should("have.attr", "open");
4253

54+
isPickerOpen(true);
55+
4356
cy.get("@datePicker")
4457
.shadow()
4558
.find("ui5-calendar")
@@ -61,6 +74,8 @@ Cypress.Commands.add("ui5DatePickerGetFirstDisplayedDate", { prevSubject: true }
6174
cy.get("@datePicker")
6275
.should("have.attr", "open");
6376

77+
isPickerOpen(true);
78+
6479
cy.get("@datePicker")
6580
.shadow()
6681
.find("ui5-calendar")
@@ -82,6 +97,8 @@ Cypress.Commands.add("ui5DatePickerGetFirstDisplayedYear", { prevSubject: true }
8297
cy.get("@datePicker")
8398
.should("have.attr", "open");
8499

100+
isPickerOpen(true);
101+
85102
cy.get("@datePicker")
86103
.shadow()
87104
.find("ui5-calendar")
@@ -102,6 +119,8 @@ Cypress.Commands.add("ui5DatePickerGetDisplayedMonth", { prevSubject: true }, (s
102119
cy.get("@datePicker")
103120
.should("have.attr", "open");
104121

122+
isPickerOpen(true);
123+
105124
cy.get("@datePicker")
106125
.shadow()
107126
.find("ui5-calendar")
@@ -123,6 +142,8 @@ Cypress.Commands.add("ui5DatePickerGetDisplayedYear", { prevSubject: true }, (su
123142
cy.get("@datePicker")
124143
.should("have.attr", "open");
125144

145+
isPickerOpen(true);
146+
126147
cy.get("@datePicker")
127148
.shadow()
128149
.find("ui5-calendar")
@@ -144,6 +165,8 @@ Cypress.Commands.add("ui5DatePickerGetNextButton", { prevSubject: true }, subjec
144165
cy.get("@datePicker")
145166
.should("have.attr", "open");
146167

168+
isPickerOpen(true);
169+
147170
cy.get("@datePicker")
148171
.shadow()
149172
.find("ui5-calendar")
@@ -163,6 +186,29 @@ Cypress.Commands.add("ui5DatePickerGetPreviousButton", { prevSubject: true }, su
163186
cy.get("@datePicker")
164187
.should("have.attr", "open");
165188

189+
isPickerOpen(true);
190+
191+
cy.get("@datePicker")
192+
.shadow()
193+
.find("ui5-calendar")
194+
.as("calendar")
195+
.should("be.visible");
196+
197+
return cy.get("@calendar")
198+
.shadow()
199+
.find(".ui5-calheader div[data-ui5-cal-header-btn-prev]")
200+
.first();
201+
});
202+
203+
Cypress.Commands.add("ui5DatePickerGetPreviousButton", { prevSubject: true }, subject => {
204+
cy.wrap(subject)
205+
.as("datePicker");
206+
207+
cy.get("@datePicker")
208+
.should("have.attr", "open");
209+
210+
isPickerOpen(true);
211+
166212
cy.get("@datePicker")
167213
.shadow()
168214
.find("ui5-calendar")
@@ -179,6 +225,8 @@ Cypress.Commands.add("ui5DatePickerGetMonthButton", { prevSubject: true }, subje
179225
cy.wrap(subject)
180226
.as("datePicker");
181227

228+
isPickerOpen(true);
229+
182230
cy.get("@datePicker")
183231
.shadow()
184232
.find("ui5-calendar")
@@ -195,6 +243,8 @@ Cypress.Commands.add("ui5DatePickerGetYearButton", { prevSubject: true }, subjec
195243
cy.wrap(subject)
196244
.as("datePicker");
197245

246+
isPickerOpen(true);
247+
198248
cy.get("@datePicker")
199249
.shadow()
200250
.find("ui5-calendar")

0 commit comments

Comments
 (0)