xmlui 0.10.21 → 0.10.22

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.
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "xmlui",
3
- "version": "0.10.21",
3
+ "version": "0.10.22",
4
4
  "sideEffects": false,
5
5
  "scripts": {
6
6
  "start-test-bed": "cd src/testing/infrastructure && xmlui start",
@@ -74,6 +74,7 @@ const FileDownloadAction_1 = require("../components-core/action/FileDownloadActi
74
74
  const FileUploadAction_1 = require("../components-core/action/FileUploadAction");
75
75
  const NavigateAction_1 = require("../components-core/action/NavigateAction");
76
76
  const TimedAction_1 = require("../components-core/action/TimedAction");
77
+ const CoreBehaviors_1 = require("../components-core/behaviors/CoreBehaviors");
77
78
  const ApiLoader_1 = require("../components-core/loader/ApiLoader");
78
79
  const ExternalDataLoader_1 = require("../components-core/loader/ExternalDataLoader");
79
80
  const MockLoaderRenderer_1 = require("../components-core/loader/MockLoaderRenderer");
@@ -149,7 +150,7 @@ class ComponentRegistry {
149
150
  * about component registrations
150
151
  */
151
152
  constructor(contributes = {}, extensionManager) {
152
- var _a, _b, _c;
153
+ var _a, _b, _c, _d;
153
154
  this.extensionManager = extensionManager;
154
155
  // --- The pool of available components
155
156
  this.pool = new Map(); // namespace -> name -> ComponentRegistryEntry;
@@ -162,6 +163,8 @@ class ComponentRegistry {
162
163
  this.actionFns = new Map();
163
164
  // --- The pool of available loader renderers
164
165
  this.loaders = new Map();
166
+ // --- The pool of available behaviors
167
+ this.behaviors = [];
165
168
  this.extensionRegistered = (extension) => {
166
169
  var _a;
167
170
  (_a = extension.components) === null || _a === void 0 ? void 0 : _a.forEach((c) => {
@@ -656,7 +659,14 @@ class ComponentRegistry {
656
659
  this.registerLoaderRenderer(ExternalDataLoader_1.externalDataLoaderRenderer);
657
660
  this.registerLoaderRenderer(MockLoaderRenderer_1.mockLoaderRenderer);
658
661
  this.registerLoaderRenderer(DataLoader_1.dataLoaderRenderer);
659
- (_c = this.extensionManager) === null || _c === void 0 ? void 0 : _c.subscribeToRegistrations(this.extensionRegistered);
662
+ this.registerBehavior(CoreBehaviors_1.tooltipBehavior);
663
+ this.registerBehavior(CoreBehaviors_1.animationBehavior);
664
+ this.registerBehavior(CoreBehaviors_1.labelBehavior);
665
+ // Register external behaviors from contributes
666
+ (_c = contributes.behaviors) === null || _c === void 0 ? void 0 : _c.forEach((behavior) => {
667
+ this.registerBehavior(behavior);
668
+ });
669
+ (_d = this.extensionManager) === null || _d === void 0 ? void 0 : _d.subscribeToRegistrations(this.extensionRegistered);
660
670
  }
661
671
  /**
662
672
  * All theme variables used by the registered components.
@@ -761,6 +771,24 @@ class ComponentRegistry {
761
771
  registerActionFn({ actionName: functionName, actionFn }) {
762
772
  this.actionFns.set(functionName, actionFn);
763
773
  }
774
+ // --- Registers a behavior
775
+ registerBehavior(behavior, location = "after", position) {
776
+ // If position is specified, insert relative to that behavior
777
+ if (position) {
778
+ const targetIndex = this.behaviors.findIndex(b => b.name === position);
779
+ if (targetIndex !== -1) {
780
+ const insertIndex = location === "before" ? targetIndex : targetIndex + 1;
781
+ this.behaviors.splice(insertIndex, 0, behavior);
782
+ return;
783
+ }
784
+ }
785
+ // Default: append to the end
786
+ this.behaviors.push(behavior);
787
+ }
788
+ // --- Returns all registered behaviors
789
+ getBehaviors() {
790
+ return this.behaviors;
791
+ }
764
792
  }
765
793
  exports.ComponentRegistry = ComponentRegistry;
766
794
  /**
@@ -13,32 +13,41 @@ exports.TabContext = (0, react_1.createContext)({
13
13
  register: (tabItem) => { },
14
14
  unRegister: (innerId) => { },
15
15
  activeTabId: "",
16
+ getTabItems: () => [],
16
17
  });
17
18
  function useTabContextValue() {
18
19
  const [tabItems, setTabItems] = (0, react_1.useState)(constants_1.EMPTY_ARRAY);
20
+ const tabItemsRef = (0, react_1.useRef)(constants_1.EMPTY_ARRAY);
21
+ tabItemsRef.current = tabItems;
19
22
  const [activeTabId, setActiveTabId] = (0, react_1.useState)("");
23
+ const register = (0, react_1.useCallback)((tabItem) => {
24
+ setTabItems((0, immer_1.default)((draft) => {
25
+ const existing = draft.findIndex((col) => col.innerId === tabItem.innerId);
26
+ if (existing < 0) {
27
+ draft.push(tabItem);
28
+ }
29
+ else {
30
+ draft[existing] = tabItem;
31
+ }
32
+ }));
33
+ }, []);
34
+ const unRegister = (0, react_1.useCallback)((innerId) => {
35
+ setTabItems((0, immer_1.default)((draft) => {
36
+ return draft.filter((col) => col.innerId !== innerId);
37
+ }));
38
+ }, []);
39
+ const getTabItems = (0, react_1.useCallback)(() => {
40
+ return tabItemsRef.current;
41
+ }, []);
20
42
  const tabContextValue = (0, react_1.useMemo)(() => {
21
43
  return {
22
- register: (tabItem) => {
23
- setTabItems((0, immer_1.default)((draft) => {
24
- const existing = draft.findIndex((col) => col.innerId === tabItem.innerId);
25
- if (existing < 0) {
26
- draft.push(tabItem);
27
- }
28
- else {
29
- draft[existing] = tabItem;
30
- }
31
- }));
32
- },
33
- unRegister: (innerId) => {
34
- setTabItems((0, immer_1.default)((draft) => {
35
- return draft.filter((col) => col.innerId !== innerId);
36
- }));
37
- },
44
+ register,
45
+ unRegister,
38
46
  activeTabId,
39
47
  setActiveTabId,
48
+ getTabItems,
40
49
  };
41
- }, [activeTabId]);
50
+ }, [register, unRegister, activeTabId, getTabItems]);
42
51
  return {
43
52
  tabItems,
44
53
  tabContextValue,
@@ -21,9 +21,10 @@ const react_tabs_1 = require("@radix-ui/react-tabs");
21
21
  const Tabs_module_scss_1 = __importDefault(require("../Tabs/Tabs.module.scss"));
22
22
  const TabContext_1 = require("./TabContext");
23
23
  exports.TabItemComponent = (0, react_2.forwardRef)(function TabItemComponent(_a, forwardedRef) {
24
+ var _b;
24
25
  var { children, label, headerRenderer, style, id, activated } = _a, rest = __rest(_a, ["children", "label", "headerRenderer", "style", "id", "activated"]);
25
26
  const innerId = (0, react_2.useId)();
26
- const { register, unRegister, activeTabId } = (0, TabContext_1.useTabContext)();
27
+ const { register, unRegister, activeTabId, getTabItems } = (0, TabContext_1.useTabContext)();
27
28
  (0, react_2.useEffect)(() => {
28
29
  register({
29
30
  label,
@@ -44,5 +45,9 @@ exports.TabItemComponent = (0, react_2.forwardRef)(function TabItemComponent(_a,
44
45
  }, [activeTabId, innerId, activated]);
45
46
  if (activeTabId !== innerId)
46
47
  return null;
47
- return ((0, react_1.createElement)(react_tabs_1.Content, Object.assign({}, rest, { key: innerId, value: innerId, className: Tabs_module_scss_1.default.tabsContent, ref: forwardedRef, style: style }), children));
48
+ // Find the index of this tab for ordering
49
+ const tabItems = getTabItems();
50
+ const tabIndex = (_b = tabItems === null || tabItems === void 0 ? void 0 : tabItems.findIndex(item => item.innerId === innerId)) !== null && _b !== void 0 ? _b : 0;
51
+ const contentOrder = tabIndex * 2 + 1;
52
+ return ((0, react_1.createElement)(react_tabs_1.Content, Object.assign({}, rest, { key: innerId, value: innerId, className: Tabs_module_scss_1.default.tabsContent, ref: forwardedRef, style: Object.assign(Object.assign({}, style), { order: contentOrder }) }), children));
48
53
  });
@@ -24,11 +24,30 @@ exports.TabsMd = (0, metadata_helpers_1.createMetadata)({
24
24
  orientation: {
25
25
  description: `This property indicates the orientation of the component. In horizontal orientation, ` +
26
26
  `the tab sections are laid out on the left side of the content panel, while in vertical ` +
27
- `orientation, the buttons are at the top.`,
27
+ `orientation, the buttons are at the top. Note: This property is ignored when ` +
28
+ `accordionView is set to true.`,
28
29
  availableValues: ["horizontal", "vertical"],
29
30
  defaultValue: TabsNative_1.defaultProps.orientation,
30
31
  valueType: "string",
31
32
  },
33
+ tabAlignment: {
34
+ description: `This property controls how tabs are aligned within the tab header container in ` +
35
+ `horizontal orientation. Use 'start' to align tabs to the left, 'end' to align to the ` +
36
+ `right, 'center' to center the tabs, and 'stretch' to make tabs fill the full width ` +
37
+ `of the header. Note: This property is ignored when orientation is set to 'vertical' ` +
38
+ `or when accordionView is enabled.`,
39
+ availableValues: ["start", "end", "center", "stretch"],
40
+ defaultValue: TabsNative_1.defaultProps.tabAlignment,
41
+ valueType: "string",
42
+ },
43
+ accordionView: {
44
+ description: `When enabled, displays tabs in an accordion-like view where tab headers are stacked ` +
45
+ `vertically and only the active tab's content is visible. Each tab header remains visible ` +
46
+ `and clicking a header opens its content while closing others. When enabled, the ` +
47
+ `orientation property is ignored.`,
48
+ defaultValue: TabsNative_1.defaultProps.accordionView,
49
+ valueType: "boolean",
50
+ },
32
51
  headerTemplate: Object.assign({}, (0, metadata_helpers_1.dComponent)(`This property declares the template for the clickable tab area.`)),
33
52
  },
34
53
  events: {
@@ -67,10 +86,10 @@ exports.TabsMd = (0, metadata_helpers_1.createMetadata)({
67
86
  },
68
87
  });
69
88
  exports.tabsComponentRenderer = (0, renderers_1.createComponentRenderer)(COMP, exports.TabsMd, ({ extractValue, node, renderChild, className, registerComponentApi, lookupEventHandler }) => {
70
- var _a, _b, _c;
89
+ var _a, _b, _c, _d, _e;
71
90
  return ((0, jsx_runtime_1.jsx)(TabsNative_1.Tabs, { id: node === null || node === void 0 ? void 0 : node.uid, className: className, headerRenderer: ((_a = node === null || node === void 0 ? void 0 : node.props) === null || _a === void 0 ? void 0 : _a.headerTemplate)
72
91
  ? (item) => ((0, jsx_runtime_1.jsx)(container_helpers_1.MemoizedItem, { node: node.props.headerTemplate, itemKey: "$header", contextVars: {
73
92
  $header: item,
74
93
  }, renderChild: renderChild }))
75
- : undefined, activeTab: extractValue((_b = node.props) === null || _b === void 0 ? void 0 : _b.activeTab), orientation: extractValue((_c = node.props) === null || _c === void 0 ? void 0 : _c.orientation), onDidChange: lookupEventHandler("didChange"), registerComponentApi: registerComponentApi, children: renderChild(node.children) }));
94
+ : undefined, activeTab: extractValue((_b = node.props) === null || _b === void 0 ? void 0 : _b.activeTab), orientation: extractValue((_c = node.props) === null || _c === void 0 ? void 0 : _c.orientation), tabAlignment: extractValue((_d = node.props) === null || _d === void 0 ? void 0 : _d.tabAlignment), accordionView: extractValue((_e = node.props) === null || _e === void 0 ? void 0 : _e.accordionView), onDidChange: lookupEventHandler("didChange"), registerComponentApi: registerComponentApi, children: renderChild(node.children) }));
76
95
  });
@@ -9,6 +9,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
9
9
  });
10
10
  };
11
11
  Object.defineProperty(exports, "__esModule", { value: true });
12
+ const component_test_helpers_1 = require("../../testing/component-test-helpers");
12
13
  const fixtures_1 = require("../../testing/fixtures");
13
14
  // =============================================================================
14
15
  // SMOKE TESTS
@@ -105,6 +106,367 @@ fixtures_1.test.describe("basic functionality", () => {
105
106
  const tabsRoot = page.getByTestId("vertical-tabs");
106
107
  yield (0, fixtures_1.expect)(tabsRoot).toHaveAttribute("data-orientation", "vertical");
107
108
  }));
109
+ fixtures_1.test.describe("tabAlignment property", () => {
110
+ (0, fixtures_1.test)("tabAlignment='start' positions tabs at the start of container (horizontal)", (_a) => __awaiter(void 0, [_a], void 0, function* ({ initTestBed, page }) {
111
+ yield initTestBed(`
112
+ <Tabs tabAlignment="start" testId="tabs">
113
+ <TabItem label="Tab 1">Content 1</TabItem>
114
+ <TabItem label="Tab 2">Content 2</TabItem>
115
+ <TabItem label="Tab 3">Content 3</TabItem>
116
+ </Tabs>
117
+ `);
118
+ const tabsContainer = page.getByTestId("tabs");
119
+ const tab1 = page.getByRole("tab", { name: "Tab 1" });
120
+ const { left: containerLeft } = yield (0, component_test_helpers_1.getBounds)(tabsContainer);
121
+ const { left: tab1Left } = yield (0, component_test_helpers_1.getBounds)(tab1);
122
+ // Tab should be near the start of the container (within a small margin for padding)
123
+ (0, fixtures_1.expect)(tab1Left - containerLeft).toBeLessThan(50);
124
+ }));
125
+ (0, fixtures_1.test)("tabAlignment='end' positions tabs at the end of container (horizontal)", (_a) => __awaiter(void 0, [_a], void 0, function* ({ initTestBed, page }) {
126
+ yield initTestBed(`
127
+ <Tabs tabAlignment="end" testId="tabs">
128
+ <TabItem label="Tab 1">Content 1</TabItem>
129
+ <TabItem label="Tab 2">Content 2</TabItem>
130
+ <TabItem label="Tab 3">Content 3</TabItem>
131
+ </Tabs>
132
+ `);
133
+ const tabsContainer = page.getByTestId("tabs");
134
+ const tab3 = page.getByRole("tab", { name: "Tab 3" });
135
+ const { right: containerRight } = yield (0, component_test_helpers_1.getBounds)(tabsContainer);
136
+ const { right: tab3Right } = yield (0, component_test_helpers_1.getBounds)(tab3);
137
+ // Last tab should be near the end of the container (within a small margin for padding)
138
+ (0, fixtures_1.expect)(containerRight - tab3Right).toBeLessThan(50);
139
+ }));
140
+ (0, fixtures_1.test)("tabAlignment='center' positions tabs in center of container (horizontal)", (_a) => __awaiter(void 0, [_a], void 0, function* ({ initTestBed, page }) {
141
+ yield initTestBed(`
142
+ <Tabs tabAlignment="center" testId="tabs" style="width: 800px">
143
+ <TabItem label="Tab 1">Content 1</TabItem>
144
+ <TabItem label="Tab 2">Content 2</TabItem>
145
+ <TabItem label="Tab 3">Content 3</TabItem>
146
+ </Tabs>
147
+ `);
148
+ const tabsContainer = page.getByTestId("tabs");
149
+ const tab1 = page.getByRole("tab", { name: "Tab 1" });
150
+ const tab3 = page.getByRole("tab", { name: "Tab 3" });
151
+ const { left: containerLeft, right: containerRight, width: containerWidth } = yield (0, component_test_helpers_1.getBounds)(tabsContainer);
152
+ const { left: tab1Left } = yield (0, component_test_helpers_1.getBounds)(tab1);
153
+ const { right: tab3Right } = yield (0, component_test_helpers_1.getBounds)(tab3);
154
+ const containerCenter = containerLeft + containerWidth / 2;
155
+ const tabsCenter = tab1Left + (tab3Right - tab1Left) / 2;
156
+ // Tabs should be centered (within a reasonable margin)
157
+ (0, fixtures_1.expect)(Math.abs(tabsCenter - containerCenter)).toBeLessThan(50);
158
+ }));
159
+ (0, fixtures_1.test)("tabAlignment='stretch' makes tabs fill container width (horizontal)", (_a) => __awaiter(void 0, [_a], void 0, function* ({ initTestBed, page }) {
160
+ yield initTestBed(`
161
+ <Tabs tabAlignment="stretch" testId="tabs" style="width: 600px">
162
+ <TabItem label="Tab 1">Content 1</TabItem>
163
+ <TabItem label="Tab 2">Content 2</TabItem>
164
+ <TabItem label="Tab 3">Content 3</TabItem>
165
+ </Tabs>
166
+ `);
167
+ const tabsContainer = page.getByTestId("tabs");
168
+ const tab1 = page.getByRole("tab", { name: "Tab 1" });
169
+ const tab3 = page.getByRole("tab", { name: "Tab 3" });
170
+ const { left: containerLeft, right: containerRight } = yield (0, component_test_helpers_1.getBounds)(tabsContainer);
171
+ const { left: tab1Left } = yield (0, component_test_helpers_1.getBounds)(tab1);
172
+ const { right: tab3Right } = yield (0, component_test_helpers_1.getBounds)(tab3);
173
+ // First tab should start near container start
174
+ (0, fixtures_1.expect)(tab1Left - containerLeft).toBeLessThan(50);
175
+ // Last tab should end near container end
176
+ (0, fixtures_1.expect)(containerRight - tab3Right).toBeLessThan(50);
177
+ }));
178
+ (0, fixtures_1.test)("tabAlignment can be dynamically changed", (_a) => __awaiter(void 0, [_a], void 0, function* ({ initTestBed, page }) {
179
+ const { testStateDriver } = yield initTestBed(`
180
+ <Fragment>
181
+ <Tabs tabAlignment="{testState ?? 'start'}" testId="tabs" style="width: 700px">
182
+ <TabItem label="Tab 1">Content 1</TabItem>
183
+ <TabItem label="Tab 2">Content 2</TabItem>
184
+ <TabItem label="Tab 3">Content 3</TabItem>
185
+ </Tabs>
186
+ <Button testId="changeBtn" onClick="testState = 'end'" />
187
+ </Fragment>
188
+ `);
189
+ const tabsContainer = page.getByTestId("tabs");
190
+ const tab1 = page.getByRole("tab", { name: "Tab 1" });
191
+ // Initially with 'start' alignment
192
+ const { left: containerLeft } = yield (0, component_test_helpers_1.getBounds)(tabsContainer);
193
+ const { left: initialTab1Left } = yield (0, component_test_helpers_1.getBounds)(tab1);
194
+ (0, fixtures_1.expect)(initialTab1Left - containerLeft).toBeLessThan(50);
195
+ // Change to 'end' alignment
196
+ yield page.getByTestId("changeBtn").click();
197
+ yield fixtures_1.expect.poll(testStateDriver.testState).toEqual("end");
198
+ // After changing to 'end', the last tab should be near the end
199
+ const tab3 = page.getByRole("tab", { name: "Tab 3" });
200
+ const { right: containerRight } = yield (0, component_test_helpers_1.getBounds)(tabsContainer);
201
+ const { right: tab3Right } = yield (0, component_test_helpers_1.getBounds)(tab3);
202
+ (0, fixtures_1.expect)(containerRight - tab3Right).toBeLessThan(50);
203
+ }));
204
+ (0, fixtures_1.test)("tabAlignment works with single tab", (_a) => __awaiter(void 0, [_a], void 0, function* ({ initTestBed, page }) {
205
+ yield initTestBed(`
206
+ <Tabs tabAlignment="center" testId="tabs" style="width: 600px">
207
+ <TabItem label="Only Tab">Only Content</TabItem>
208
+ </Tabs>
209
+ `);
210
+ const tab = page.getByRole("tab", { name: "Only Tab" });
211
+ yield (0, fixtures_1.expect)(tab).toBeVisible();
212
+ // Verify the tab renders and is functional
213
+ yield (0, fixtures_1.expect)(page.getByText("Only Content")).toBeVisible();
214
+ }));
215
+ (0, fixtures_1.test)("tabAlignment='stretch' distributes multiple tabs evenly", (_a) => __awaiter(void 0, [_a], void 0, function* ({ initTestBed, page }) {
216
+ yield initTestBed(`
217
+ <Tabs tabAlignment="stretch" testId="tabs" style="width: 600px">
218
+ <TabItem label="Tab 1">Content 1</TabItem>
219
+ <TabItem label="Tab 2">Content 2</TabItem>
220
+ <TabItem label="Tab 3">Content 3</TabItem>
221
+ </Tabs>
222
+ `);
223
+ const tab1 = page.getByRole("tab", { name: "Tab 1" });
224
+ const tab2 = page.getByRole("tab", { name: "Tab 2" });
225
+ const tab3 = page.getByRole("tab", { name: "Tab 3" });
226
+ const { width: tab1Width } = yield (0, component_test_helpers_1.getBounds)(tab1);
227
+ const { width: tab2Width } = yield (0, component_test_helpers_1.getBounds)(tab2);
228
+ const { width: tab3Width } = yield (0, component_test_helpers_1.getBounds)(tab3);
229
+ // All tabs should have similar widths when stretched (within 20px tolerance for text differences)
230
+ (0, fixtures_1.expect)(Math.abs(tab1Width - tab2Width)).toBeLessThan(20);
231
+ (0, fixtures_1.expect)(Math.abs(tab2Width - tab3Width)).toBeLessThan(20);
232
+ }));
233
+ (0, fixtures_1.test)("tabAlignment handles null value gracefully", (_a) => __awaiter(void 0, [_a], void 0, function* ({ initTestBed, page }) {
234
+ yield initTestBed(`
235
+ <Tabs tabAlignment="{null}" testId="tabs">
236
+ <TabItem label="Tab 1">Content 1</TabItem>
237
+ <TabItem label="Tab 2">Content 2</TabItem>
238
+ </Tabs>
239
+ `);
240
+ // Should fall back to default 'start' alignment
241
+ const tabsContainer = page.getByTestId("tabs");
242
+ const tab1 = page.getByRole("tab", { name: "Tab 1" });
243
+ yield (0, fixtures_1.expect)(tab1).toBeVisible();
244
+ const { left: containerLeft } = yield (0, component_test_helpers_1.getBounds)(tabsContainer);
245
+ const { left: tab1Left } = yield (0, component_test_helpers_1.getBounds)(tab1);
246
+ // Should behave like 'start' alignment
247
+ (0, fixtures_1.expect)(tab1Left - containerLeft).toBeLessThan(50);
248
+ }));
249
+ (0, fixtures_1.test)("tabAlignment handles undefined value gracefully", (_a) => __awaiter(void 0, [_a], void 0, function* ({ initTestBed, page }) {
250
+ yield initTestBed(`
251
+ <Tabs tabAlignment="{undefined}" testId="tabs">
252
+ <TabItem label="Tab 1">Content 1</TabItem>
253
+ <TabItem label="Tab 2">Content 2</TabItem>
254
+ </Tabs>
255
+ `);
256
+ // Should fall back to default 'start' alignment
257
+ const tabsContainer = page.getByTestId("tabs");
258
+ const tab1 = page.getByRole("tab", { name: "Tab 1" });
259
+ yield (0, fixtures_1.expect)(tab1).toBeVisible();
260
+ const { left: containerLeft } = yield (0, component_test_helpers_1.getBounds)(tabsContainer);
261
+ const { left: tab1Left } = yield (0, component_test_helpers_1.getBounds)(tab1);
262
+ // Should behave like 'start' alignment
263
+ (0, fixtures_1.expect)(tab1Left - containerLeft).toBeLessThan(50);
264
+ }));
265
+ (0, fixtures_1.test)("tabAlignment maintains functionality when tabs are clicked", (_a) => __awaiter(void 0, [_a], void 0, function* ({ initTestBed, page }) {
266
+ yield initTestBed(`
267
+ <Tabs tabAlignment="center" testId="tabs" style="width: 700px">
268
+ <TabItem label="Tab 1">Content 1</TabItem>
269
+ <TabItem label="Tab 2">Content 2</TabItem>
270
+ <TabItem label="Tab 3">Content 3</TabItem>
271
+ </Tabs>
272
+ `);
273
+ // Verify all tabs are visible
274
+ yield (0, fixtures_1.expect)(page.getByRole("tab", { name: "Tab 1" })).toBeVisible();
275
+ yield (0, fixtures_1.expect)(page.getByRole("tab", { name: "Tab 2" })).toBeVisible();
276
+ yield (0, fixtures_1.expect)(page.getByRole("tab", { name: "Tab 3" })).toBeVisible();
277
+ // Click different tabs and verify content changes
278
+ yield page.getByRole("tab", { name: "Tab 2" }).click();
279
+ yield (0, fixtures_1.expect)(page.getByText("Content 2")).toBeVisible();
280
+ yield (0, fixtures_1.expect)(page.getByText("Content 1")).not.toBeVisible();
281
+ yield page.getByRole("tab", { name: "Tab 3" }).click();
282
+ yield (0, fixtures_1.expect)(page.getByText("Content 3")).toBeVisible();
283
+ yield (0, fixtures_1.expect)(page.getByText("Content 2")).not.toBeVisible();
284
+ yield page.getByRole("tab", { name: "Tab 1" }).click();
285
+ yield (0, fixtures_1.expect)(page.getByText("Content 1")).toBeVisible();
286
+ yield (0, fixtures_1.expect)(page.getByText("Content 3")).not.toBeVisible();
287
+ }));
288
+ });
289
+ fixtures_1.test.describe("accordionView property", () => {
290
+ (0, fixtures_1.test)("accordionView renders all tab headers when true", (_a) => __awaiter(void 0, [_a], void 0, function* ({ initTestBed, page }) {
291
+ yield initTestBed(`
292
+ <Tabs accordionView="true" testId="tabs">
293
+ <TabItem label="Tab 1">Content 1</TabItem>
294
+ <TabItem label="Tab 2">Content 2</TabItem>
295
+ <TabItem label="Tab 3">Content 3</TabItem>
296
+ </Tabs>
297
+ `);
298
+ // All tab headers should be visible
299
+ yield (0, fixtures_1.expect)(page.getByRole("tab", { name: "Tab 1" })).toBeVisible();
300
+ yield (0, fixtures_1.expect)(page.getByRole("tab", { name: "Tab 2" })).toBeVisible();
301
+ yield (0, fixtures_1.expect)(page.getByRole("tab", { name: "Tab 3" })).toBeVisible();
302
+ }));
303
+ (0, fixtures_1.test)("accordionView shows only active tab content", (_a) => __awaiter(void 0, [_a], void 0, function* ({ initTestBed, page }) {
304
+ yield initTestBed(`
305
+ <Tabs accordionView="true" testId="tabs">
306
+ <TabItem label="Tab 1">Content 1</TabItem>
307
+ <TabItem label="Tab 2">Content 2</TabItem>
308
+ <TabItem label="Tab 3">Content 3</TabItem>
309
+ </Tabs>
310
+ `);
311
+ // Only first tab content should be visible (default active)
312
+ yield (0, fixtures_1.expect)(page.getByText("Content 1")).toBeVisible();
313
+ yield (0, fixtures_1.expect)(page.getByText("Content 2")).not.toBeVisible();
314
+ yield (0, fixtures_1.expect)(page.getByText("Content 3")).not.toBeVisible();
315
+ }));
316
+ (0, fixtures_1.test)("accordionView positions active tab header above its content", (_a) => __awaiter(void 0, [_a], void 0, function* ({ initTestBed, page }) {
317
+ yield initTestBed(`
318
+ <Tabs accordionView="true" testId="tabs">
319
+ <TabItem label="Tab 1">Content 1</TabItem>
320
+ <TabItem label="Tab 2">Content 2</TabItem>
321
+ <TabItem label="Tab 3">Content 3</TabItem>
322
+ </Tabs>
323
+ `);
324
+ const tab1Header = page.getByRole("tab", { name: "Tab 1" });
325
+ const tab1Content = page.getByText("Content 1");
326
+ const { bottom: headerBottom } = yield (0, component_test_helpers_1.getBounds)(tab1Header);
327
+ const { top: contentTop } = yield (0, component_test_helpers_1.getBounds)(tab1Content);
328
+ // Header should be above content (header bottom should be less than or near content top)
329
+ (0, fixtures_1.expect)(headerBottom).toBeLessThanOrEqual(contentTop + 5);
330
+ }));
331
+ (0, fixtures_1.test)("accordionView positions active tab content above next tab header", (_a) => __awaiter(void 0, [_a], void 0, function* ({ initTestBed, page }) {
332
+ yield initTestBed(`
333
+ <Tabs accordionView="true" testId="tabs">
334
+ <TabItem label="Tab 1">Content 1</TabItem>
335
+ <TabItem label="Tab 2">Content 2</TabItem>
336
+ <TabItem label="Tab 3">Content 3</TabItem>
337
+ </Tabs>
338
+ `);
339
+ const tab1Content = page.getByText("Content 1");
340
+ const tab2Header = page.getByRole("tab", { name: "Tab 2" });
341
+ const { bottom: contentBottom } = yield (0, component_test_helpers_1.getBounds)(tab1Content);
342
+ const { top: nextHeaderTop } = yield (0, component_test_helpers_1.getBounds)(tab2Header);
343
+ // Content should be above next header (content bottom should be less than or near next header top)
344
+ (0, fixtures_1.expect)(contentBottom).toBeLessThanOrEqual(nextHeaderTop + 5);
345
+ }));
346
+ (0, fixtures_1.test)("accordionView maintains order: header1 -> content1 -> header2 -> content2 (when tab2 active)", (_a) => __awaiter(void 0, [_a], void 0, function* ({ initTestBed, page }) {
347
+ yield initTestBed(`
348
+ <Tabs accordionView="true" activeTab="1" testId="tabs">
349
+ <TabItem label="Tab 1">Content 1</TabItem>
350
+ <TabItem label="Tab 2">Content 2</TabItem>
351
+ <TabItem label="Tab 3">Content 3</TabItem>
352
+ </Tabs>
353
+ `);
354
+ const tab1Header = page.getByRole("tab", { name: "Tab 1" });
355
+ const tab2Header = page.getByRole("tab", { name: "Tab 2" });
356
+ const tab2Content = page.getByText("Content 2");
357
+ const tab3Header = page.getByRole("tab", { name: "Tab 3" });
358
+ const { top: tab1Top } = yield (0, component_test_helpers_1.getBounds)(tab1Header);
359
+ const { top: tab2Top } = yield (0, component_test_helpers_1.getBounds)(tab2Header);
360
+ const { top: content2Top } = yield (0, component_test_helpers_1.getBounds)(tab2Content);
361
+ const { top: tab3Top } = yield (0, component_test_helpers_1.getBounds)(tab3Header);
362
+ // Verify vertical ordering
363
+ (0, fixtures_1.expect)(tab1Top).toBeLessThan(tab2Top);
364
+ (0, fixtures_1.expect)(tab2Top).toBeLessThan(content2Top);
365
+ (0, fixtures_1.expect)(content2Top).toBeLessThan(tab3Top);
366
+ }));
367
+ (0, fixtures_1.test)("accordionView switches content when different tab is clicked", (_a) => __awaiter(void 0, [_a], void 0, function* ({ initTestBed, page }) {
368
+ yield initTestBed(`
369
+ <Tabs accordionView="true" testId="tabs">
370
+ <TabItem label="Tab 1">Content 1</TabItem>
371
+ <TabItem label="Tab 2">Content 2</TabItem>
372
+ <TabItem label="Tab 3">Content 3</TabItem>
373
+ </Tabs>
374
+ `);
375
+ // Initially Tab 1 is active
376
+ yield (0, fixtures_1.expect)(page.getByText("Content 1")).toBeVisible();
377
+ yield (0, fixtures_1.expect)(page.getByText("Content 2")).not.toBeVisible();
378
+ // Click Tab 2
379
+ yield page.getByRole("tab", { name: "Tab 2" }).click();
380
+ yield (0, fixtures_1.expect)(page.getByText("Content 2")).toBeVisible();
381
+ yield (0, fixtures_1.expect)(page.getByText("Content 1")).not.toBeVisible();
382
+ // Verify Tab 2 content is below Tab 2 header
383
+ const tab2Header = page.getByRole("tab", { name: "Tab 2" });
384
+ const tab2Content = page.getByText("Content 2");
385
+ const { bottom: headerBottom } = yield (0, component_test_helpers_1.getBounds)(tab2Header);
386
+ const { top: contentTop } = yield (0, component_test_helpers_1.getBounds)(tab2Content);
387
+ (0, fixtures_1.expect)(headerBottom).toBeLessThanOrEqual(contentTop + 5);
388
+ }));
389
+ (0, fixtures_1.test)("accordionView works with dynamic activeTab changes", (_a) => __awaiter(void 0, [_a], void 0, function* ({ initTestBed, page }) {
390
+ const { testStateDriver } = yield initTestBed(`
391
+ <Fragment>
392
+ <Tabs accordionView="true" activeTab="{testState ?? 0}" testId="tabs">
393
+ <TabItem label="Tab 1">Content 1</TabItem>
394
+ <TabItem label="Tab 2">Content 2</TabItem>
395
+ <TabItem label="Tab 3">Content 3</TabItem>
396
+ </Tabs>
397
+ <Button testId="tab2Btn" onClick="testState = 1" />
398
+ <Button testId="tab3Btn" onClick="testState = 2" />
399
+ </Fragment>
400
+ `);
401
+ // Initially Content 1 is visible
402
+ yield (0, fixtures_1.expect)(page.getByText("Content 1")).toBeVisible();
403
+ yield (0, fixtures_1.expect)(page.getByText("Content 2")).not.toBeVisible();
404
+ yield (0, fixtures_1.expect)(page.getByText("Content 3")).not.toBeVisible();
405
+ // Switch to Tab 2
406
+ yield page.getByTestId("tab2Btn").click();
407
+ yield (0, fixtures_1.expect)(page.getByText("Content 2")).toBeVisible();
408
+ yield (0, fixtures_1.expect)(page.getByText("Content 1")).not.toBeVisible();
409
+ yield (0, fixtures_1.expect)(page.getByText("Content 3")).not.toBeVisible();
410
+ // Verify ordering for Tab 2
411
+ const tab2Header = page.getByRole("tab", { name: "Tab 2" });
412
+ const tab2Content = page.getByText("Content 2");
413
+ const tab3Header = page.getByRole("tab", { name: "Tab 3" });
414
+ const { top: tab2Top } = yield (0, component_test_helpers_1.getBounds)(tab2Header);
415
+ const { top: content2Top } = yield (0, component_test_helpers_1.getBounds)(tab2Content);
416
+ const { top: tab3Top } = yield (0, component_test_helpers_1.getBounds)(tab3Header);
417
+ (0, fixtures_1.expect)(tab2Top).toBeLessThan(content2Top);
418
+ (0, fixtures_1.expect)(content2Top).toBeLessThan(tab3Top);
419
+ // Switch to Tab 3
420
+ yield page.getByTestId("tab3Btn").click();
421
+ yield (0, fixtures_1.expect)(page.getByText("Content 3")).toBeVisible();
422
+ yield (0, fixtures_1.expect)(page.getByText("Content 1")).not.toBeVisible();
423
+ yield (0, fixtures_1.expect)(page.getByText("Content 2")).not.toBeVisible();
424
+ }));
425
+ (0, fixtures_1.test)("accordionView=false renders standard tabs (non-accordion)", (_a) => __awaiter(void 0, [_a], void 0, function* ({ initTestBed, page }) {
426
+ yield initTestBed(`
427
+ <Tabs accordionView="false" testId="tabs">
428
+ <TabItem label="Tab 1">Content 1</TabItem>
429
+ <TabItem label="Tab 2">Content 2</TabItem>
430
+ <TabItem label="Tab 3">Content 3</TabItem>
431
+ </Tabs>
432
+ `);
433
+ const tab1Header = page.getByRole("tab", { name: "Tab 1" });
434
+ const tab2Header = page.getByRole("tab", { name: "Tab 2" });
435
+ const tab1Content = page.getByText("Content 1");
436
+ // In standard mode (non-accordion), all headers should be visible
437
+ yield (0, fixtures_1.expect)(tab1Header).toBeVisible();
438
+ yield (0, fixtures_1.expect)(tab2Header).toBeVisible();
439
+ // Only active tab content should be visible
440
+ yield (0, fixtures_1.expect)(tab1Content).toBeVisible();
441
+ yield (0, fixtures_1.expect)(page.getByText("Content 2")).not.toBeVisible();
442
+ // Tab content should NOT be interleaved between headers
443
+ // (unlike accordion view where content appears between headers)
444
+ const { bottom: tab1Bottom } = yield (0, component_test_helpers_1.getBounds)(tab1Header);
445
+ const { top: tab2Top } = yield (0, component_test_helpers_1.getBounds)(tab2Header);
446
+ const { top: contentTop } = yield (0, component_test_helpers_1.getBounds)(tab1Content);
447
+ // Content should NOT be between the two headers
448
+ // Either tab2 is next to tab1 (horizontal) or below tab1 but before content (vertical)
449
+ const isContentBetweenHeaders = contentTop > tab1Bottom && contentTop < tab2Top;
450
+ (0, fixtures_1.expect)(isContentBetweenHeaders).toBe(false);
451
+ }));
452
+ (0, fixtures_1.test)("accordionView handles null value gracefully (defaults to false)", (_a) => __awaiter(void 0, [_a], void 0, function* ({ initTestBed, page }) {
453
+ yield initTestBed(`
454
+ <Tabs accordionView="{null}" testId="tabs">
455
+ <TabItem label="Tab 1">Content 1</TabItem>
456
+ <TabItem label="Tab 2">Content 2</TabItem>
457
+ </Tabs>
458
+ `);
459
+ // Should render as standard horizontal tabs
460
+ const tab1Header = page.getByRole("tab", { name: "Tab 1" });
461
+ const tab2Header = page.getByRole("tab", { name: "Tab 2" });
462
+ yield (0, fixtures_1.expect)(tab1Header).toBeVisible();
463
+ yield (0, fixtures_1.expect)(tab2Header).toBeVisible();
464
+ // Headers should be horizontally aligned
465
+ const { top: tab1Top } = yield (0, component_test_helpers_1.getBounds)(tab1Header);
466
+ const { top: tab2Top } = yield (0, component_test_helpers_1.getBounds)(tab2Header);
467
+ (0, fixtures_1.expect)(Math.abs(tab1Top - tab2Top)).toBeLessThan(10);
468
+ }));
469
+ });
108
470
  });
109
471
  // =============================================================================
110
472
  // HEADER TEMPLATE TESTS