@gjsify/adwaita-web 0.31.0 → 0.32.0

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.
Files changed (176) hide show
  1. package/dist/adwaita-web.css +786 -272
  2. package/dist/adwaita-web.css.map +1 -1
  3. package/lib/types/adw-about-dialog.spec.d.ts +1 -0
  4. package/lib/types/adw-action-rows.spec.d.ts +1 -0
  5. package/lib/types/adw-avatar.spec.d.ts +1 -0
  6. package/lib/types/adw-banner.spec.d.ts +1 -0
  7. package/lib/types/adw-button-content.spec.d.ts +1 -0
  8. package/lib/types/adw-carousel.spec.d.ts +1 -0
  9. package/lib/types/adw-checks.spec.d.ts +1 -0
  10. package/lib/types/adw-header-bar.spec.d.ts +1 -0
  11. package/lib/types/adw-icon.spec.d.ts +1 -0
  12. package/lib/types/adw-progress-bar.spec.d.ts +1 -0
  13. package/lib/types/adw-sidebar.spec.d.ts +1 -0
  14. package/lib/types/adw-switch.spec.d.ts +1 -0
  15. package/lib/types/adw-wrap-box.spec.d.ts +1 -0
  16. package/lib/types/bottom-sheet.spec.d.ts +1 -0
  17. package/lib/types/breakpoints.d.ts +22 -0
  18. package/lib/types/breakpoints.spec.d.ts +1 -0
  19. package/lib/types/chrome.spec.d.ts +1 -0
  20. package/lib/types/elements/adw-about-dialog.d.ts +47 -8
  21. package/lib/types/elements/adw-action-row.d.ts +35 -0
  22. package/lib/types/elements/adw-avatar.d.ts +11 -0
  23. package/lib/types/elements/adw-bottom-sheet.d.ts +33 -4
  24. package/lib/types/elements/adw-button-content.d.ts +12 -0
  25. package/lib/types/elements/adw-button-row.d.ts +10 -1
  26. package/lib/types/elements/adw-carousel.d.ts +111 -8
  27. package/lib/types/elements/adw-checks.d.ts +57 -0
  28. package/lib/types/elements/adw-clamp.d.ts +4 -1
  29. package/lib/types/elements/adw-data-grid.d.ts +11 -31
  30. package/lib/types/elements/adw-drop-down.d.ts +17 -20
  31. package/lib/types/elements/adw-entry-row.d.ts +93 -3
  32. package/lib/types/elements/adw-expander-row.d.ts +7 -1
  33. package/lib/types/elements/adw-header-bar.d.ts +15 -0
  34. package/lib/types/elements/adw-icon.d.ts +25 -0
  35. package/lib/types/elements/adw-inline-view-switcher.d.ts +37 -11
  36. package/lib/types/elements/adw-menu-button.d.ts +6 -6
  37. package/lib/types/elements/adw-navigation-split-view.d.ts +6 -0
  38. package/lib/types/elements/adw-navigation-view.d.ts +63 -20
  39. package/lib/types/elements/adw-overlay-split-view.d.ts +38 -0
  40. package/lib/types/elements/adw-password-entry-row.d.ts +19 -10
  41. package/lib/types/elements/adw-popover.d.ts +58 -0
  42. package/lib/types/elements/adw-preferences-dialog.d.ts +57 -2
  43. package/lib/types/elements/adw-preferences-group.d.ts +31 -0
  44. package/lib/types/elements/adw-progress-bar.d.ts +38 -0
  45. package/lib/types/elements/adw-sidebar.d.ts +37 -5
  46. package/lib/types/elements/adw-split-button.d.ts +53 -7
  47. package/lib/types/elements/adw-switch-row.d.ts +11 -2
  48. package/lib/types/elements/adw-switch.d.ts +15 -0
  49. package/lib/types/elements/adw-tab-view.d.ts +129 -12
  50. package/lib/types/elements/adw-toolbar-view.d.ts +5 -0
  51. package/lib/types/elements/adw-view-stack.d.ts +27 -19
  52. package/lib/types/elements/adw-view-switcher-bar.d.ts +23 -7
  53. package/lib/types/elements/adw-view-switcher.d.ts +22 -11
  54. package/lib/types/elements/adw-window-title.d.ts +3 -1
  55. package/lib/types/elements/view-switcher-dom.d.ts +44 -0
  56. package/lib/types/entry-rows.spec.d.ts +1 -0
  57. package/lib/types/index.d.ts +7 -0
  58. package/lib/types/navigation-view.spec.d.ts +1 -0
  59. package/lib/types/preferences-search.d.ts +20 -0
  60. package/lib/types/preferences.spec.d.ts +1 -0
  61. package/lib/types/split-button.spec.d.ts +1 -0
  62. package/lib/types/split-views.spec.d.ts +1 -0
  63. package/lib/types/styles.generated.d.ts +1 -1
  64. package/lib/types/tab-view.spec.d.ts +1 -0
  65. package/lib/types/view-stack.spec.d.ts +1 -0
  66. package/lib/types/view-switcher.spec.d.ts +1 -0
  67. package/package.json +6 -6
  68. package/scss/_banner.scss +13 -0
  69. package/scss/_bottom_sheet.scss +5 -6
  70. package/scss/_button.scss +3 -26
  71. package/scss/_button_content.scss +28 -0
  72. package/scss/_checks.scss +228 -0
  73. package/scss/_clamp.scss +9 -3
  74. package/scss/_data_grid.scss +15 -7
  75. package/scss/_drop_down.scss +12 -55
  76. package/scss/_expander_row.scss +5 -50
  77. package/scss/_icon.scss +51 -0
  78. package/scss/_icons.generated.scss +6 -0
  79. package/scss/_inline_view_switcher.scss +34 -0
  80. package/scss/_labels.scss +135 -0
  81. package/scss/_menu_button.scss +10 -70
  82. package/scss/_popover.scss +186 -0
  83. package/scss/_preferences.scss +29 -0
  84. package/scss/_preferences_dialog.scss +17 -0
  85. package/scss/_progress_bar.scss +161 -0
  86. package/scss/_reset.scss +18 -7
  87. package/scss/_spinner.scss +7 -3
  88. package/scss/_split_button.scss +6 -50
  89. package/scss/_switch.scss +90 -0
  90. package/scss/_switch_row.scss +6 -52
  91. package/scss/_tab_view.scss +64 -0
  92. package/scss/_theme.scss +10 -0
  93. package/scss/_toolbar_view.scss +74 -0
  94. package/scss/_variables.scss +34 -0
  95. package/scss/_view_switcher.scss +40 -0
  96. package/scss/_view_switcher_bar.scss +37 -0
  97. package/scss/_wrap_box.scss +56 -6
  98. package/scss/adwaita-skin.scss +23 -0
  99. package/src/adw-about-dialog.spec.ts +350 -0
  100. package/src/adw-action-rows.spec.ts +342 -0
  101. package/src/adw-avatar.spec.ts +139 -0
  102. package/src/adw-banner.spec.ts +152 -0
  103. package/src/adw-button-content.spec.ts +183 -0
  104. package/src/adw-button-row.spec.ts +12 -6
  105. package/src/adw-carousel.spec.ts +389 -0
  106. package/src/adw-checks.spec.ts +368 -0
  107. package/src/adw-drop-down.spec.ts +123 -0
  108. package/src/adw-header-bar.spec.ts +83 -0
  109. package/src/adw-icon.spec.ts +167 -0
  110. package/src/adw-progress-bar.spec.ts +173 -0
  111. package/src/adw-sidebar.spec.ts +332 -0
  112. package/src/adw-switch.spec.ts +242 -0
  113. package/src/adw-tab-view.spec.ts +12 -3
  114. package/src/adw-view-switcher-bar.spec.ts +61 -13
  115. package/src/adw-wrap-box.spec.ts +194 -0
  116. package/src/bottom-sheet.spec.ts +325 -0
  117. package/src/breakpoints.spec.ts +147 -0
  118. package/src/breakpoints.ts +67 -0
  119. package/src/chrome.spec.ts +301 -0
  120. package/src/elements/adw-about-dialog.ts +223 -163
  121. package/src/elements/adw-action-row.ts +120 -10
  122. package/src/elements/adw-avatar.ts +48 -54
  123. package/src/elements/adw-banner.ts +46 -10
  124. package/src/elements/adw-bottom-sheet.ts +145 -60
  125. package/src/elements/adw-button-content.ts +107 -33
  126. package/src/elements/adw-button-row.ts +61 -37
  127. package/src/elements/adw-button.ts +5 -7
  128. package/src/elements/adw-carousel.ts +467 -105
  129. package/src/elements/adw-checks.ts +299 -0
  130. package/src/elements/adw-clamp.ts +80 -10
  131. package/src/elements/adw-combo-row.ts +15 -7
  132. package/src/elements/adw-data-grid.ts +43 -91
  133. package/src/elements/adw-drop-down.ts +117 -128
  134. package/src/elements/adw-entry-row.ts +404 -36
  135. package/src/elements/adw-expander-row.ts +61 -29
  136. package/src/elements/adw-header-bar.ts +31 -6
  137. package/src/elements/adw-icon.ts +148 -0
  138. package/src/elements/adw-inline-view-switcher.ts +240 -129
  139. package/src/elements/adw-menu-button.ts +93 -70
  140. package/src/elements/adw-navigation-split-view.ts +48 -7
  141. package/src/elements/adw-navigation-view.ts +286 -148
  142. package/src/elements/adw-overlay-split-view.ts +164 -14
  143. package/src/elements/adw-password-entry-row.ts +111 -89
  144. package/src/elements/adw-popover.ts +283 -0
  145. package/src/elements/adw-preferences-dialog.ts +120 -21
  146. package/src/elements/adw-preferences-group.ts +128 -22
  147. package/src/elements/adw-progress-bar.ts +189 -0
  148. package/src/elements/adw-sidebar.ts +301 -142
  149. package/src/elements/adw-spin-row.ts +11 -5
  150. package/src/elements/adw-spinner.ts +23 -8
  151. package/src/elements/adw-split-button.ts +371 -92
  152. package/src/elements/adw-status-page.ts +9 -9
  153. package/src/elements/adw-switch-row.ts +100 -30
  154. package/src/elements/adw-switch.ts +129 -0
  155. package/src/elements/adw-tab-view.ts +682 -129
  156. package/src/elements/adw-toggle-group.ts +5 -7
  157. package/src/elements/adw-toolbar-view.ts +84 -21
  158. package/src/elements/adw-view-stack.ts +162 -85
  159. package/src/elements/adw-view-switcher-bar.ts +163 -81
  160. package/src/elements/adw-view-switcher.ts +231 -112
  161. package/src/elements/adw-window-title.ts +44 -11
  162. package/src/elements/adw-wrap-box.ts +131 -35
  163. package/src/elements/view-switcher-dom.ts +108 -0
  164. package/src/entry-rows.spec.ts +391 -0
  165. package/src/index.ts +23 -0
  166. package/src/navigation-view.spec.ts +435 -0
  167. package/src/preferences-search.ts +135 -0
  168. package/src/preferences.spec.ts +250 -0
  169. package/src/source-view/adw-source-view.ts +2 -5
  170. package/src/split-button.spec.ts +470 -0
  171. package/src/split-views.spec.ts +309 -0
  172. package/src/styles.generated.ts +1 -1
  173. package/src/tab-view.spec.ts +451 -0
  174. package/src/test.browser.mts +49 -0
  175. package/src/view-stack.spec.ts +272 -0
  176. package/src/view-switcher.spec.ts +446 -0
@@ -0,0 +1,309 @@
1
+ // The two split views against the shared conformance vectors.
2
+ //
3
+ // The element now delegates the property interplay to
4
+ // `OverlaySplitViewState`, so this suite asserts the half a DOM renderer owns:
5
+ // that the attributes reach the state, and that what the state derives actually
6
+ // reaches the DOM.
7
+ //
8
+ // The rule it pins down first is the one the element got wrong for its whole
9
+ // life: `Adw.OverlaySplitView:show-sidebar` defaults to TRUE
10
+ // (adw-overlay-split-view.c:974-976), and an HTML boolean attribute is
11
+ // presence-only, which can only express a FALSE default. Bare markup showed no
12
+ // sidebar where GTK shows one.
13
+ import { describe, expect, it } from '@gjsify/unit';
14
+
15
+ import { SIDEBAR_BOUNDS_VECTORS } from '@gjsify/adwaita-core/conformance';
16
+ import { OVERLAY_COLLAPSE_VECTORS } from '@gjsify/adwaita-core/conformance';
17
+
18
+ import type { AdwNavigationSplitView } from './elements/adw-navigation-split-view.js';
19
+ import type { AdwOverlaySplitView } from './elements/adw-overlay-split-view.js';
20
+
21
+ /** Mount a split view from markup and return it. */
22
+ function mount(attrs = ''): { view: AdwOverlaySplitView; host: HTMLElement } {
23
+ const host = document.createElement('div');
24
+ document.body.appendChild(host);
25
+ host.innerHTML =
26
+ `<adw-overlay-split-view ${attrs}>` +
27
+ '<div slot="sidebar">s</div><div slot="content">c</div>' +
28
+ '</adw-overlay-split-view>';
29
+ return { view: host.querySelector('adw-overlay-split-view') as AdwOverlaySplitView, host };
30
+ }
31
+
32
+ /** The parts of `OverlaySplitViewSnapshot` a DOM renderer actually publishes. */
33
+ interface DomSnapshot {
34
+ showSidebar: boolean;
35
+ collapsed: boolean;
36
+ shieldVisible: boolean;
37
+ sidebarFocusable: boolean;
38
+ contentFocusable: boolean;
39
+ }
40
+
41
+ /**
42
+ * Read the whole derived state back OUT of the DOM.
43
+ *
44
+ * Asserting only `showSidebar`/`collapsed` is what let `pin-sidebar` fall out of
45
+ * `observedAttributes` unnoticed: both of those still agreed, because the vectors
46
+ * only ever set the attribute in markup, where `connectedCallback` reads it
47
+ * directly. Every field the element publishes is checked here instead.
48
+ *
49
+ * `showProgress` is the one snapshot field with no DOM surface — the element
50
+ * paints the reveal from the `collapsed`/`show-sidebar` classes rather than a
51
+ * numeric progress (a continuous reveal is not implemented). `shieldVisible` is
52
+ * derived from it (`collapsed && showProgress > 0`), so a progress bug still
53
+ * shows up here.
54
+ */
55
+ function domSnapshot(view: AdwOverlaySplitView): DomSnapshot {
56
+ const backdrop = view.querySelector('.adw-osv-backdrop') as HTMLElement;
57
+ const sidebar = view.querySelector('.adw-osv-sidebar') as HTMLElement;
58
+ const content = view.querySelector('.adw-osv-content') as HTMLElement;
59
+ return {
60
+ showSidebar: view.classList.contains('show-sidebar'),
61
+ collapsed: view.classList.contains('collapsed'),
62
+ shieldVisible: !backdrop.hidden,
63
+ sidebarFocusable: sidebar.getAttribute('aria-hidden') === 'false',
64
+ contentFocusable: content.getAttribute('aria-hidden') === 'false',
65
+ };
66
+ }
67
+
68
+ /** The same five fields, taken from a conformance snapshot. */
69
+ function expectedSnapshot(snapshot: {
70
+ showSidebar: boolean;
71
+ collapsed: boolean;
72
+ shieldVisible: boolean;
73
+ sidebarFocusable: boolean;
74
+ contentFocusable: boolean;
75
+ }): DomSnapshot {
76
+ return {
77
+ showSidebar: snapshot.showSidebar,
78
+ collapsed: snapshot.collapsed,
79
+ shieldVisible: snapshot.shieldVisible,
80
+ sidebarFocusable: snapshot.sidebarFocusable,
81
+ contentFocusable: snapshot.contentFocusable,
82
+ };
83
+ }
84
+
85
+ export const AdwSplitViewsTest = async () => {
86
+ await describe('adw-overlay-split-view defaults (Adw.OverlaySplitView properties)', async () => {
87
+ await it('shows the sidebar with no attributes at all, like GTK does', () => {
88
+ const { view, host } = mount();
89
+ expect(view.showSidebar).toBe(true);
90
+ expect(view.classList.contains('show-sidebar')).toBe(true);
91
+ host.remove();
92
+ });
93
+
94
+ await it('takes show-sidebar="false" as the explicit off switch', () => {
95
+ const { view, host } = mount('show-sidebar="false"');
96
+ expect(view.showSidebar).toBe(false);
97
+ expect(view.classList.contains('show-sidebar')).toBe(false);
98
+ host.remove();
99
+ });
100
+
101
+ await it('still treats a bare show-sidebar as true', () => {
102
+ const { view, host } = mount('show-sidebar');
103
+ expect(view.showSidebar).toBe(true);
104
+ host.remove();
105
+ });
106
+
107
+ await it('round-trips the property setter through the attribute', () => {
108
+ const { view, host } = mount();
109
+ view.showSidebar = false;
110
+ expect(view.showSidebar).toBe(false);
111
+ view.showSidebar = true;
112
+ expect(view.showSidebar).toBe(true);
113
+ host.remove();
114
+ });
115
+ });
116
+
117
+ await describe('adw-overlay-split-view collapse (shared conformance vectors)', async () => {
118
+ for (const vector of OVERLAY_COLLAPSE_VECTORS) {
119
+ const { initial, before, setCollapsed, after, rule } = vector;
120
+ // Only the rows a DOM element can express: `pinSidebar` and the
121
+ // starting `collapsed`/`showSidebar` all have attributes.
122
+ const attrs = [
123
+ initial.collapsed ? 'collapsed' : '',
124
+ initial.showSidebar === false ? 'show-sidebar="false"' : '',
125
+ initial.pinSidebar ? 'pin-sidebar' : '',
126
+ ]
127
+ .filter(Boolean)
128
+ .join(' ');
129
+
130
+ await it(`${JSON.stringify(initial)} → collapsed=${setCollapsed} — ${rule}`, () => {
131
+ const { view, host } = mount(attrs);
132
+ expect(view.showSidebar).toBe(before.showSidebar);
133
+ expect(view.collapsed).toBe(before.collapsed);
134
+ expect(domSnapshot(view)).toStrictEqual(expectedSnapshot(before));
135
+
136
+ view.collapsed = setCollapsed;
137
+ expect(view.showSidebar).toBe(after.showSidebar);
138
+ expect(view.collapsed).toBe(after.collapsed);
139
+ expect(domSnapshot(view)).toStrictEqual(expectedSnapshot(after));
140
+ host.remove();
141
+ });
142
+ }
143
+
144
+ await it('honours pin-sidebar set AFTER construction, not only in markup', () => {
145
+ // The regression the vector loop above cannot see: it only ever puts
146
+ // `pin-sidebar` in the markup, which `connectedCallback` reads directly.
147
+ // With the attribute missing from `observedAttributes` the
148
+ // `_readAttribute` branch was dead, so pinning a live element silently
149
+ // did nothing and the next collapse still hid the sidebar.
150
+ const { view, host } = mount();
151
+ view.setAttribute('pin-sidebar', '');
152
+
153
+ view.collapsed = true;
154
+ expect(domSnapshot(view)).toStrictEqual({
155
+ showSidebar: true,
156
+ collapsed: true,
157
+ shieldVisible: true,
158
+ sidebarFocusable: true,
159
+ contentFocusable: false,
160
+ });
161
+ host.remove();
162
+ });
163
+
164
+ await it('unpins again when the attribute is removed', () => {
165
+ const { view, host } = mount('pin-sidebar');
166
+ view.removeAttribute('pin-sidebar');
167
+
168
+ view.collapsed = true;
169
+ expect(domSnapshot(view)).toStrictEqual({
170
+ showSidebar: false,
171
+ collapsed: true,
172
+ shieldVisible: false,
173
+ sidebarFocusable: false,
174
+ contentFocusable: true,
175
+ });
176
+ host.remove();
177
+ });
178
+ });
179
+
180
+ await describe('adw-overlay-split-view derived state reaches the DOM', async () => {
181
+ await it('hides the shield unless collapsed AND revealed', () => {
182
+ const { view, host } = mount();
183
+ const backdrop = view.querySelector('.adw-osv-backdrop') as HTMLElement;
184
+ // Expanded: no shield, whatever the sidebar is doing.
185
+ expect(backdrop.hidden).toBe(true);
186
+ host.remove();
187
+ });
188
+
189
+ await it('marks the off-screen pane aria-hidden once collapsing hides it', () => {
190
+ // Collapsing is a TRANSITION: it auto-hides an unpinned sidebar, so
191
+ // content becomes the reachable pane. Constructing collapsed does
192
+ // NOT — `<adw-overlay-split-view collapsed>` keeps the sidebar shown,
193
+ // the same as building the GTK widget with both properties set.
194
+ const { view, host } = mount();
195
+ expect((view.querySelector('.adw-osv-sidebar') as HTMLElement).getAttribute('aria-hidden')).toBe('false');
196
+
197
+ view.collapsed = true;
198
+ expect((view.querySelector('.adw-osv-sidebar') as HTMLElement).getAttribute('aria-hidden')).toBe('true');
199
+ expect((view.querySelector('.adw-osv-content') as HTMLElement).getAttribute('aria-hidden')).toBe('false');
200
+ host.remove();
201
+ });
202
+
203
+ await it('constructing collapsed keeps the sidebar shown, unlike collapsing later', () => {
204
+ const { view, host } = mount('collapsed');
205
+ expect(view.showSidebar).toBe(true);
206
+ host.remove();
207
+ });
208
+
209
+ await it('leaves both panes reachable while expanded', () => {
210
+ const { view, host } = mount();
211
+ expect((view.querySelector('.adw-osv-sidebar') as HTMLElement).getAttribute('aria-hidden')).toBe('false');
212
+ expect((view.querySelector('.adw-osv-content') as HTMLElement).getAttribute('aria-hidden')).toBe('false');
213
+ host.remove();
214
+ });
215
+ });
216
+ await describe('adw-overlay-split-view sidebar width (Adw.OverlaySplitView properties)', async () => {
217
+ await it('defaults to the WIDGET values, not to an app preference', () => {
218
+ // 180 / 280 / 0.25 — adw-overlay-split-view.c:1036-1075. These read
219
+ // 280 / 400 / 0.30 before, which are the values the three.js and
220
+ // canvas2d showcases set explicitly in their .blp, so the storybook's
221
+ // GTK and browser sides showed different sidebars.
222
+ const { view, host } = mount();
223
+ expect(view.minSidebarWidth).toBe(180);
224
+ expect(view.maxSidebarWidth).toBe(280);
225
+ expect(view.sidebarWidthFraction).toBe(0.25);
226
+ host.remove();
227
+ });
228
+
229
+ await it('writes the normalised bounds, not the raw attributes', () => {
230
+ const { view, host } = mount('min-sidebar-width="300" max-sidebar-width="200"');
231
+ const sidebar = view.querySelector('.adw-osv-sidebar') as HTMLElement;
232
+ // libadwaita never lets max fall below min, so both land at 300.
233
+ // CSS on its own resolves the conflict the other way (min-width wins
234
+ // over max-width), which would render a different widget.
235
+ expect(sidebar.style.minWidth).toBe('300px');
236
+ expect(sidebar.style.maxWidth).toBe('300px');
237
+ host.remove();
238
+ });
239
+
240
+ for (const vector of SIDEBAR_BOUNDS_VECTORS) {
241
+ // Only the rows expressible in CSS pixels: the element has no dpi or
242
+ // length-unit attribute, so `sp`-at-another-dpi rows stay in core.
243
+ if (vector.spec.sidebarWidthUnit !== undefined || vector.spec.dpi !== undefined) continue;
244
+ if (vector.sidebarChildMin !== 0 || !vector.ceil) continue;
245
+
246
+ const attrs = [
247
+ vector.spec.minSidebarWidth !== undefined ? `min-sidebar-width="${vector.spec.minSidebarWidth}"` : '',
248
+ vector.spec.maxSidebarWidth !== undefined ? `max-sidebar-width="${vector.spec.maxSidebarWidth}"` : '',
249
+ ]
250
+ .filter(Boolean)
251
+ .join(' ');
252
+
253
+ await it(`bounds ${JSON.stringify(vector.spec)} → ${vector.min}/${vector.max} — ${vector.rule}`, () => {
254
+ const { view, host } = mount(attrs);
255
+ const sidebar = view.querySelector('.adw-osv-sidebar') as HTMLElement;
256
+ expect(sidebar.style.minWidth).toBe(`${vector.min}px`);
257
+ expect(sidebar.style.maxWidth).toBe(`${vector.max}px`);
258
+ host.remove();
259
+ });
260
+ }
261
+ });
262
+ await describe('adw-navigation-split-view sidebar width (Adw.NavigationSplitView)', async () => {
263
+ const mountNav = (attrs = '') => {
264
+ const host = document.createElement('div');
265
+ document.body.appendChild(host);
266
+ host.innerHTML =
267
+ `<adw-navigation-split-view ${attrs}>` +
268
+ '<div slot="sidebar">s</div><div slot="content">c</div>' +
269
+ '</adw-navigation-split-view>';
270
+ return {
271
+ view: host.querySelector('adw-navigation-split-view') as AdwNavigationSplitView,
272
+ host,
273
+ };
274
+ };
275
+
276
+ await it('bounds the sidebar even with no attributes', () => {
277
+ // An absent attribute used to mean "no bound at all"; the widget
278
+ // always has 180 / 280.
279
+ const { view, host } = mountNav();
280
+ const sidebar = view.querySelector('.adw-nsv-sidebar') as HTMLElement;
281
+ expect(sidebar.style.minWidth).toBe('180px');
282
+ expect(sidebar.style.maxWidth).toBe('280px');
283
+ host.remove();
284
+ });
285
+
286
+ await it('normalises an inverted pair the way GLib CLAMP does', () => {
287
+ const { view, host } = mountNav('min-sidebar-width="320" max-sidebar-width="200"');
288
+ const sidebar = view.querySelector('.adw-nsv-sidebar') as HTMLElement;
289
+ expect(sidebar.style.minWidth).toBe('320px');
290
+ expect(sidebar.style.maxWidth).toBe('320px');
291
+ host.remove();
292
+ });
293
+
294
+ await it('drops the caps while collapsed, so the pane fills the view', () => {
295
+ const { view, host } = mountNav('collapsed');
296
+ const sidebar = view.querySelector('.adw-nsv-sidebar') as HTMLElement;
297
+ expect(sidebar.style.minWidth).toBe('');
298
+ expect(sidebar.style.maxWidth).toBe('');
299
+ host.remove();
300
+ });
301
+
302
+ await it('falls back to the default when the attribute is not a number', () => {
303
+ const { view, host } = mountNav('min-sidebar-width="wide"');
304
+ const sidebar = view.querySelector('.adw-nsv-sidebar') as HTMLElement;
305
+ expect(sidebar.style.minWidth).toBe('180px');
306
+ host.remove();
307
+ });
308
+ });
309
+ };