@gjsify/adwaita-web 0.36.0 → 0.37.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 (146) hide show
  1. package/dist/adwaita-web.css +433 -33
  2. package/dist/adwaita-web.css.map +1 -1
  3. package/lib/types/adw-shortcut-label.spec.d.ts +2 -0
  4. package/lib/types/breakpoints.d.ts +10 -12
  5. package/lib/types/elements/adw-about-dialog.d.ts +19 -36
  6. package/lib/types/elements/adw-action-row.d.ts +9 -12
  7. package/lib/types/elements/adw-alert-dialog.d.ts +0 -12
  8. package/lib/types/elements/adw-avatar.d.ts +1 -0
  9. package/lib/types/elements/adw-bottom-sheet.d.ts +7 -12
  10. package/lib/types/elements/adw-button-content.d.ts +3 -4
  11. package/lib/types/elements/adw-button-row.d.ts +1 -1
  12. package/lib/types/elements/adw-carousel.d.ts +28 -49
  13. package/lib/types/elements/adw-checks.d.ts +2 -8
  14. package/lib/types/elements/adw-combo-row.d.ts +6 -6
  15. package/lib/types/elements/adw-data-grid.d.ts +5 -10
  16. package/lib/types/elements/adw-drop-down.d.ts +3 -11
  17. package/lib/types/elements/adw-entry-row.d.ts +20 -23
  18. package/lib/types/elements/adw-icon.d.ts +2 -4
  19. package/lib/types/elements/adw-inline-view-switcher.d.ts +18 -31
  20. package/lib/types/elements/adw-navigation-split-view.d.ts +7 -12
  21. package/lib/types/elements/adw-navigation-view.d.ts +2 -10
  22. package/lib/types/elements/adw-overlay-split-view.d.ts +28 -52
  23. package/lib/types/elements/adw-password-entry-row.d.ts +4 -5
  24. package/lib/types/elements/adw-popover.d.ts +2 -7
  25. package/lib/types/elements/adw-preferences-dialog.d.ts +21 -34
  26. package/lib/types/elements/adw-preferences-group.d.ts +12 -14
  27. package/lib/types/elements/adw-progress-bar.d.ts +3 -4
  28. package/lib/types/elements/adw-shortcut-label.d.ts +20 -0
  29. package/lib/types/elements/adw-sidebar.d.ts +5 -10
  30. package/lib/types/elements/adw-spinner.d.ts +3 -5
  31. package/lib/types/elements/adw-split-button.d.ts +6 -16
  32. package/lib/types/elements/adw-switch-row.d.ts +3 -3
  33. package/lib/types/elements/adw-tab-view.d.ts +16 -26
  34. package/lib/types/elements/adw-toast-overlay.d.ts +8 -13
  35. package/lib/types/elements/adw-view-stack.d.ts +7 -17
  36. package/lib/types/elements/adw-view-switcher-bar.d.ts +4 -8
  37. package/lib/types/elements/adw-view-switcher.d.ts +18 -38
  38. package/lib/types/elements/adw-wrap-box.d.ts +7 -17
  39. package/lib/types/elements/view-switcher-dom.d.ts +10 -13
  40. package/lib/types/index.d.ts +1 -0
  41. package/lib/types/source-view/hex.d.ts +7 -13
  42. package/lib/types/style-classes.spec.d.ts +11 -0
  43. package/lib/types/styles.generated.d.ts +1 -1
  44. package/package.json +6 -6
  45. package/scss/_about_dialog.scss +3 -2
  46. package/scss/_action_row.scss +33 -0
  47. package/scss/_avatar.scss +12 -0
  48. package/scss/_button.scss +44 -22
  49. package/scss/_card.scss +11 -8
  50. package/scss/_checks.scss +2 -4
  51. package/scss/_dialog.scss +3 -2
  52. package/scss/_headerbar.scss +23 -0
  53. package/scss/_popover.scss +8 -4
  54. package/scss/_preferences.scss +7 -7
  55. package/scss/_progress_bar.scss +9 -6
  56. package/scss/_reset.scss +1 -1
  57. package/scss/_shortcut_label.scss +80 -0
  58. package/scss/_sidebar.scss +57 -0
  59. package/scss/_status_page.scss +20 -0
  60. package/scss/_style_classes.scss +407 -0
  61. package/scss/_switch.scss +4 -3
  62. package/scss/_variables.scss +25 -0
  63. package/scss/adwaita-skin.scss +12 -0
  64. package/src/adw-about-dialog.spec.ts +34 -49
  65. package/src/adw-action-rows.spec.ts +25 -33
  66. package/src/adw-avatar.spec.ts +51 -20
  67. package/src/adw-banner.spec.ts +5 -8
  68. package/src/adw-button-content.spec.ts +9 -12
  69. package/src/adw-button-row.spec.ts +2 -4
  70. package/src/adw-carousel.spec.ts +40 -53
  71. package/src/adw-checks.spec.ts +36 -47
  72. package/src/adw-drop-down.spec.ts +25 -32
  73. package/src/adw-header-bar.spec.ts +5 -7
  74. package/src/adw-icon.spec.ts +23 -32
  75. package/src/adw-progress-bar.spec.ts +10 -12
  76. package/src/adw-row-state.spec.ts +9 -10
  77. package/src/adw-shortcut-label.spec.ts +112 -0
  78. package/src/adw-sidebar.spec.ts +17 -24
  79. package/src/adw-switch.spec.ts +11 -17
  80. package/src/adw-tab-view.spec.ts +7 -10
  81. package/src/adw-toast-overlay.spec.ts +7 -11
  82. package/src/adw-view-switcher-bar.spec.ts +10 -14
  83. package/src/adw-wrap-box.spec.ts +25 -36
  84. package/src/bottom-sheet.spec.ts +30 -36
  85. package/src/breakpoints.ts +15 -18
  86. package/src/chrome.spec.ts +13 -21
  87. package/src/elements/adw-about-dialog.ts +71 -114
  88. package/src/elements/adw-action-row.ts +22 -37
  89. package/src/elements/adw-alert-dialog.ts +25 -50
  90. package/src/elements/adw-avatar.ts +40 -15
  91. package/src/elements/adw-banner.ts +14 -18
  92. package/src/elements/adw-bottom-sheet.ts +31 -52
  93. package/src/elements/adw-button-content.ts +30 -37
  94. package/src/elements/adw-button-row.ts +14 -24
  95. package/src/elements/adw-carousel.ts +87 -148
  96. package/src/elements/adw-checks.ts +43 -56
  97. package/src/elements/adw-clamp.ts +8 -9
  98. package/src/elements/adw-combo-row.ts +17 -25
  99. package/src/elements/adw-data-grid.ts +26 -38
  100. package/src/elements/adw-dialog.ts +25 -56
  101. package/src/elements/adw-drop-down.ts +31 -51
  102. package/src/elements/adw-entry-row.ts +39 -46
  103. package/src/elements/adw-expander-row.ts +30 -53
  104. package/src/elements/adw-icon.ts +30 -53
  105. package/src/elements/adw-inline-view-switcher.ts +38 -67
  106. package/src/elements/adw-menu-button.ts +28 -58
  107. package/src/elements/adw-navigation-split-view.ts +35 -47
  108. package/src/elements/adw-navigation-view.ts +12 -28
  109. package/src/elements/adw-overlay-split-view.ts +100 -175
  110. package/src/elements/adw-password-entry-row.ts +18 -28
  111. package/src/elements/adw-popover.ts +28 -44
  112. package/src/elements/adw-preferences-dialog.ts +41 -66
  113. package/src/elements/adw-preferences-group.ts +33 -46
  114. package/src/elements/adw-progress-bar.ts +27 -47
  115. package/src/elements/adw-shortcut-label.ts +138 -0
  116. package/src/elements/adw-sidebar.ts +31 -39
  117. package/src/elements/adw-spin-row.ts +7 -14
  118. package/src/elements/adw-spinner.ts +31 -43
  119. package/src/elements/adw-split-button.ts +45 -75
  120. package/src/elements/adw-switch-row.ts +29 -46
  121. package/src/elements/adw-switch.ts +28 -47
  122. package/src/elements/adw-tab-view.ts +62 -103
  123. package/src/elements/adw-toast-overlay.ts +28 -35
  124. package/src/elements/adw-toggle-group.ts +9 -16
  125. package/src/elements/adw-view-stack.ts +32 -51
  126. package/src/elements/adw-view-switcher-bar.ts +26 -50
  127. package/src/elements/adw-view-switcher.ts +60 -110
  128. package/src/elements/adw-window-title.ts +4 -4
  129. package/src/elements/adw-wrap-box.ts +33 -59
  130. package/src/elements/view-switcher-dom.ts +15 -22
  131. package/src/entry-rows.spec.ts +18 -24
  132. package/src/index.ts +13 -15
  133. package/src/navigation-view.spec.ts +7 -10
  134. package/src/preferences-search.ts +11 -15
  135. package/src/preferences.spec.ts +13 -15
  136. package/src/source-view/adw-source-view.ts +6 -19
  137. package/src/source-view/hex.ts +7 -13
  138. package/src/split-button.spec.ts +32 -44
  139. package/src/split-views.spec.ts +83 -149
  140. package/src/style-classes.spec.ts +181 -0
  141. package/src/style-isolation.spec.ts +3 -6
  142. package/src/styles.generated.ts +1 -1
  143. package/src/tab-view.spec.ts +26 -35
  144. package/src/test.browser.mts +4 -0
  145. package/src/view-stack.spec.ts +23 -34
  146. package/src/view-switcher.spec.ts +56 -84
@@ -3,20 +3,16 @@
3
3
  // vectors the NativeScript renderer asserts against
4
4
  // (`@gjsify/adwaita-core/conformance`).
5
5
  //
6
- // What this suite exists to catch, in the words of what it found:
7
- // - NEITHER renderer hid an empty TITLE. `string_is_not_empty` is bound to the
8
- // title label as much as to the subtitle (adw-action-row.ui:49-53 vs :71-75),
9
- // and the browser port hand-rolled the subtitle-only half of that block six
10
- // times over.
11
- // - a programmatic `switchRow.active = true` emitted nothing here, because the
12
- // DOM does not fire `change` for a scripted `.checked =` — while the
13
- // NativeScript port, whose `checkedChange` does fire, emitted. One C source,
14
- // two opposite answers, and nothing comparing them.
15
- // - clicking anywhere but the handle of a switch row did nothing, though
16
- // `adw_switch_row_init` makes the whole row the switch's activator.
17
- // - `<adw-button-row activatable="false">` was honoured, an opt-out libadwaita
18
- // does not have — and `<adw-action-row activatable="false">` meant the
19
- // OPPOSITE, because that element reads the attribute by presence.
6
+ // The rules it pins, each of which one renderer had wrong:
7
+ // - `string_is_not_empty` in `adw-action-row.ui` is bound to the TITLE label as much
8
+ // as to the subtitle, so an empty title hides too;
9
+ // - the DOM fires no `change` for a scripted `.checked =`, so a programmatic
10
+ // `switchRow.active = true` must be made to notify anyway — NativeScript's
11
+ // `checkedChange` does fire, and one C source must not give two answers;
12
+ // - `adw_switch_row_init` makes the WHOLE row the switch's activator;
13
+ // - libadwaita has no activatable opt-out for a button row, and
14
+ // `<adw-action-row activatable="false">` reads by PRESENCE, so it means the
15
+ // opposite of what it says.
20
16
  import { describe, expect, it } from '@gjsify/unit';
21
17
 
22
18
  import {
@@ -35,9 +31,8 @@ import type { AdwSwitchRow } from './elements/adw-switch-row.js';
35
31
  import type { AdwWindowTitle } from './elements/adw-window-title.js';
36
32
 
37
33
  /**
38
- * Mount an element imperatively — never through parsed HTML, because several
39
- * label vectors hinge on exact whitespace (`' '`, `'\t'`) that an attribute
40
- * value in markup is not a reliable carrier for.
34
+ * Mount an element imperatively, never through parsed HTML: several label vectors hinge
35
+ * on exact whitespace (`' '`, `'\t'`) that markup is not a reliable carrier for.
41
36
  */
42
37
  function mount<T extends HTMLElement>(tag: string): { el: T; host: HTMLElement } {
43
38
  const host = document.createElement('div');
@@ -144,13 +139,11 @@ export const AdwActionRowsTest = async () => {
144
139
  await it('reads `activatable` by PRESENCE, like every other boolean attribute here', () => {
145
140
  const { el: row, host } = mount<AdwActionRow>('adw-action-row');
146
141
  row.setAttribute('activatable', 'false');
147
- // The HTML boolean-attribute convention (`<input disabled="false">`
148
- // is disabled), which is how every other boolean attribute in this
149
- // package is read. NOT a libadwaita rule — libadwaita has a typed
150
- // gboolean and no attribute parsing at all — so it is pinned here
151
- // rather than in the cross-renderer vectors. Pinned so the next
152
- // reader does not "fix" it into the string comparison
153
- // <adw-button-row> used to carry, which gave one markup two
142
+ // The HTML boolean-attribute convention (`<input disabled="false">` IS
143
+ // disabled), how every other boolean attribute in this package is read. NOT a
144
+ // libadwaita rule — libadwaita has a typed gboolean and no attribute parsing —
145
+ // so it is pinned here rather than in the cross-renderer vectors, and pinned so
146
+ // nobody "fixes" it into a string comparison that would give one markup two
154
147
  // opposite meanings inside one package.
155
148
  expect(row.activatable).toBe(true);
156
149
  host.remove();
@@ -183,9 +176,9 @@ export const AdwActionRowsTest = async () => {
183
176
 
184
177
  widget.click();
185
178
 
186
- // One click, and no row activation: GtkListBox does not emit
187
- // `row-activated` for a click a child widget claimed. Re-clicking the
188
- // widget here would also bounce back into the row's own listener.
179
+ // One click, no row activation: GtkListBox does not emit `row-activated` for a
180
+ // click a child widget claimed, and re-clicking the widget here would bounce
181
+ // back into the row's own listener.
189
182
  expect(widgetClicks).toBe(1);
190
183
  expect(activations.length).toBe(0);
191
184
  host.remove();
@@ -257,16 +250,15 @@ export const AdwActionRowsTest = async () => {
257
250
  setAttr(row, 'start-icon-name', vector.startIconName);
258
251
  setAttr(row, 'end-icon-name', vector.endIconName);
259
252
 
260
- // The two `image.icon.{start,end}` nodes are <adw-icon> now, not
261
- // hand-rolled decorative spans.
253
+ // The two `image.icon.{start,end}` nodes are <adw-icon>, not hand-rolled
254
+ // decorative spans.
262
255
  const icons = Array.from(row.querySelectorAll('adw-icon')) as HTMLElement[];
263
256
  expect(icons.length).toBe(2);
264
257
  expect(!icons[0]!.hidden).toBe(vector.startIconVisible);
265
258
  expect(!icons[1]!.hidden).toBe(vector.endIconVisible);
266
- // The mask class is the NORMALIZED name: the generated classes
267
- // never carry `-symbolic`, and this row used to interpolate the
268
- // raw one — so `start-icon-name="list-add-symbolic"` asked for a
269
- // class that has never existed and drew an empty box.
259
+ // The mask class is the NORMALIZED name — the generated classes never
260
+ // carry `-symbolic`, so interpolating the raw one asks for a class that has
261
+ // never existed and draws an empty box.
270
262
  if (vector.startIconVisible) {
271
263
  expect(icons[0]!.classList.contains(`adw-icon--${normalizeIconName(vector.startIconName)}`)).toBe(
272
264
  true,
@@ -1,12 +1,10 @@
1
1
  // DOM-level conformance tests for <adw-avatar>, driven by the SAME vectors the
2
2
  // NativeScript renderer asserts against (`@gjsify/adwaita-core/conformance`).
3
3
  //
4
- // The two renderers used to carry independent copies of the initials + colour
5
- // derivation, and the copies had drifted apart AND away from libadwaita:
6
- // this element rendered "AD" for `text="Ada"` where GTK and NS render "A", and
7
- // both hashed UTF-16 code units where GLib hashes UTF-8 bytes, so every accented
8
- // name was painted the wrong colour. Nothing failed, because nothing compared
9
- // them. This suite is that comparison.
4
+ // Two rules independent copies of the initials + colour derivation drifted from: GTK
5
+ // takes ONE initial from a single-word `text`, and the colour hash runs over UTF-8 BYTES
6
+ // as GLib's does — hashing UTF-16 code units paints every accented name the wrong
7
+ // colour.
10
8
  import { describe, expect, it } from '@gjsify/unit';
11
9
 
12
10
  import { AVATAR_MODE_VECTORS } from '@gjsify/adwaita-core/conformance';
@@ -140,37 +138,70 @@ export const AdwAvatarTest = async () => {
140
138
  });
141
139
 
142
140
  await describe('<adw-avatar> mode (update_visibility, adw-avatar.c:117)', async () => {
143
- // The `hasCustomImage` rows are not reachable through this element: it
144
- // passes `hasCustomImage: false` unconditionally, because it has no
145
- // custom-image support at all — `Adw.Avatar:custom-image` is unported.
146
- // Named below rather than silently filtered.
141
+ // A 1x1 transparent GIF: a real, decodable image, so `custom-image` is
142
+ // exercised through the same path a photo would take — and no network.
143
+ const PIXEL = 'data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7';
144
+
147
145
  for (const { hasCustomImage, showInitials, text, mode, rule } of AVATAR_MODE_VECTORS) {
148
- if (hasCustomImage) continue;
149
- await it(`initials=${showInitials} "${text}" → ${mode} — ${rule}`, () => {
146
+ await it(`image=${hasCustomImage} initials=${showInitials} "${text}" → ${mode} — ${rule}`, () => {
150
147
  const host = document.createElement('div');
151
148
  document.body.appendChild(host);
152
149
  const avatar = document.createElement('adw-avatar');
153
150
  if (showInitials) avatar.setAttribute('show-initials', '');
154
151
  if (text) avatar.setAttribute('text', text);
152
+ if (hasCustomImage) avatar.setAttribute('custom-image', PIXEL);
155
153
  host.appendChild(avatar);
156
154
 
157
- // Exactly ONE of the two reachable modes is drawn — the point of
158
- // the table is the precedence, and "both visible at once" is the
155
+ // Exactly ONE of the three modes is drawn — the point of the
156
+ // table is the precedence, and "two visible at once" is the
159
157
  // failure it guards against.
160
158
  const initials = (avatar.querySelector('.adw-avatar-text') as HTMLElement).hidden === false;
161
159
  const icon = (avatar.querySelector('.adw-avatar-icon') as HTMLElement).hidden === false;
160
+ const image = (avatar.querySelector('.adw-avatar-custom-image') as HTMLElement).hidden === false;
162
161
  expect(initials).toBe(mode === 'initials');
163
162
  expect(icon).toBe(mode === 'icon');
163
+ expect(image).toBe(mode === 'image');
164
164
  host.remove();
165
165
  });
166
166
  }
167
167
 
168
- await it('leaves out only the custom-image rows, which this element cannot reach', () => {
169
- const unreachable = AVATAR_MODE_VECTORS.filter((vector) => vector.hasCustomImage);
170
- expect(unreachable.length).toBeGreaterThan(0);
171
- // Every one of them is an `image` outcome, i.e. the whole mode is
172
- // missing rather than a corner of it.
173
- for (const vector of unreachable) expect(vector.mode).toBe('image');
168
+ await it('drops the custom image again when the attribute goes away', () => {
169
+ // The mode is re-derived on every attribute change, so removing the
170
+ // image has to fall back to whichever mode the remaining state picks
171
+ // — this is where a port that only ever ADDS the image node fails.
172
+ const host = document.createElement('div');
173
+ document.body.appendChild(host);
174
+ const avatar = document.createElement('adw-avatar');
175
+ avatar.setAttribute('show-initials', '');
176
+ avatar.setAttribute('text', 'Ada Lovelace');
177
+ avatar.setAttribute('custom-image', PIXEL);
178
+ host.appendChild(avatar);
179
+
180
+ expect((avatar.querySelector('.adw-avatar-custom-image') as HTMLElement).hidden).toBe(false);
181
+
182
+ avatar.removeAttribute('custom-image');
183
+ expect((avatar.querySelector('.adw-avatar-custom-image') as HTMLElement).hidden).toBe(true);
184
+ expect((avatar.querySelector('.adw-avatar-text') as HTMLElement).hidden).toBe(false);
185
+
186
+ host.remove();
187
+ });
188
+
189
+ await it('fills the circle with the image rather than letterboxing it', () => {
190
+ // `object-fit` is the whole reason the image looks like an avatar and
191
+ // not like a photo in a round frame — a class-name assertion cannot
192
+ // see it, and it is one word away from being wrong.
193
+ const host = document.createElement('div');
194
+ document.body.appendChild(host);
195
+ const avatar = document.createElement('adw-avatar');
196
+ avatar.setAttribute('custom-image', PIXEL);
197
+ avatar.setAttribute('size', '64');
198
+ host.appendChild(avatar);
199
+
200
+ const image = avatar.querySelector('.adw-avatar-custom-image') as HTMLElement;
201
+ expect(getComputedStyle(image).objectFit).toBe('cover');
202
+ expect(Math.round(image.getBoundingClientRect().width)).toBe(64);
203
+
204
+ host.remove();
174
205
  });
175
206
  });
176
207
  };
@@ -1,13 +1,10 @@
1
1
  // DOM-level conformance tests for <adw-banner>, driven by the SAME vectors the
2
2
  // NativeScript renderer asserts against (`@gjsify/adwaita-core/conformance`).
3
3
  //
4
- // The two renderers carried independent banner logic and had drifted apart AND
5
- // away from libadwaita: `revealed` was opt-in here (right) and initialised TRUE
6
- // on NativeScript with no `visibility` written at all (wrong), `use-markup` was
7
- // opt-in in both with a comment here asserting that WAS the C default (it is
8
- // TRUE, adw-banner.c:422-425), `button-style` was in neither, and the mnemonic
9
- // marker the template pins onto the button (adw-banner.ui:33) was painted as a
10
- // literal underscore. Nothing failed, because nothing compared them.
4
+ // The defaults independent banner logic drifted from, all of them in
5
+ // `ADW_BANNER_DEFAULTS`: `revealed` is FALSE, `use-markup` is TRUE (opting it in inverts
6
+ // the C), `button-style` exists, and the mnemonic marker the template pins onto the
7
+ // button is a marker, not a literal underscore to paint.
11
8
  import { describe, expect, it } from '@gjsify/unit';
12
9
 
13
10
  import { ADW_BANNER_DEFAULTS } from '@gjsify/adwaita-core';
@@ -64,7 +61,7 @@ export const AdwBannerTest = async () => {
64
61
  });
65
62
 
66
63
  await describe('adw-banner use-markup — a DELIBERATE departure from the C default', async () => {
67
- // The C default is unambiguously TRUE (adw-banner.c:422-425) and core
64
+ // The C default is unambiguously TRUE and core
68
65
  // carries it as the spec value. This renderer does NOT adopt it: Pango
69
66
  // markup is not HTML, and defaulting a `title` attribute to `innerHTML`
70
67
  // would make an injection sink out of a widget that is not one in GTK.
@@ -2,15 +2,12 @@
2
2
  // vectors the NativeScript renderer asserts against
3
3
  // (`@gjsify/adwaita-core/conformance`).
4
4
  //
5
- // THE STYLE CLASS IS WHY THIS SUITE EXISTS. `AdwButtonContent` puts
6
- // `image-text-button` on the button hosting it (adw-button-content.c:115) and
7
- // removes it on unroot (:126); the stylesheet gives that class
8
- // `padding-left/right: 9px` where a plain text button has 17px
9
- // (_buttons.scss:77-80 against :72-75). `grep -rn "image-text-button"` over both
10
- // renderer trees returned NOTHING, so every icon+label button in this package
11
- // was drawn with the wrong horizontal padding for its whole life. `use-underline`
12
- // was absent, and an empty `icon-name` hid the image where the C sets
13
- // `image-missing` (:355-356).
5
+ // The STYLE CLASS is why this suite exists: `AdwButtonContent` puts
6
+ // `image-text-button` on the button hosting it and removes it on unroot, and
7
+ // `_buttons.scss` gives that class 9px horizontal padding where a plain text button has
8
+ // 17px — so a renderer that never sets the class draws every icon+label button with the
9
+ // wrong padding. Also pinned: `use-underline`, and that an empty `icon-name` shows
10
+ // `image-missing` rather than hiding the image.
14
11
  import { describe, expect, it } from '@gjsify/unit';
15
12
 
16
13
  import { BUTTON_CONTENT_DEFAULT_VECTORS } from '@gjsify/adwaita-core/conformance';
@@ -93,8 +90,8 @@ export const AdwButtonContentTest = async () => {
93
90
  host.appendChild(split);
94
91
 
95
92
  // The class goes on the `splitbutton` node, which is what
96
- // `splitbutton.image-text-button > button` (:499-507) selects —
97
- // a different declaration block from the plain-button one.
93
+ // `splitbutton.image-text-button > button` selects — a different declaration
94
+ // block from the plain-button one.
98
95
  expect(split.classList.contains(BUTTON_CONTENT_STYLE_CLASS)).toBe(true);
99
96
  expect(action.classList.contains(BUTTON_CONTENT_STYLE_CLASS)).toBe(false);
100
97
  // …and the padding lands on the INNER button, which is what that
@@ -134,7 +131,7 @@ export const AdwButtonContentTest = async () => {
134
131
  maskName === '' ? 0 : 1,
135
132
  );
136
133
  // The image node is never hidden — `gtk_widget_set_visible` is
137
- // called on the LABEL only (:300, :398).
134
+ // called on the LABEL only.
138
135
  expect(icon.hidden).toBe(false);
139
136
  host.remove();
140
137
  });
@@ -36,17 +36,15 @@ export const AdwButtonRowTest = async () => {
36
36
 
37
37
  expect(errors.length).toBe(0);
38
38
  expect(row.textContent).toContain('Add account');
39
- // The start icon span carries the adw-icon class derived from the name.
40
39
  expect(row.querySelector('.adw-icon--list-add') !== null).toBe(true);
41
- // Activatable by default.
42
40
  expect(row.classList.contains('activatable')).toBe(true);
43
41
  host.remove();
44
42
  });
45
43
 
46
44
  // DELETED: `honours activatable="false"`. It was green, and what it
47
45
  // pinned was an INVENTION — `Adw.ButtonRow` is always activatable
48
- // (`<property name="activatable">True</property>`, adw-button-row.ui:5;
49
- // "AdwButtonRow is always activatable.", adw-button-row.c:31), and the
46
+ // (`<property name="activatable">True</property>`, adw-button-row.ui;
47
+ // "AdwButtonRow is always activatable."), and the
50
48
  // class exposes no property, no setter and no getter for it. The opt-out
51
49
  // also gave one markup two opposite meanings inside one package, because
52
50
  // <adw-action-row> reads `activatable` by PRESENCE — so
@@ -1,15 +1,9 @@
1
1
  // DOM-level conformance tests for <adw-carousel>, driven by the SAME vectors the
2
2
  // NativeScript renderer asserts against (`@gjsify/adwaita-core/conformance`).
3
3
  //
4
- // The two renderers used to carry independent, differently-wrong copies of the
5
- // carousel's arithmetic, and nothing compared them: this element defaulted
6
- // `allow-scroll-wheel` to FALSE where libadwaita defaults it TRUE (so a plain
7
- // <adw-carousel> ignored the wheel while its own header claimed the opposite),
8
- // resolved a half-way position to the LATER page where every libadwaita lookup
9
- // takes the earlier one, dropped `<adw-carousel position="2">` on the floor,
10
- // dropped the horizontal-delta fallback its comment described, and emitted
11
- // `page-changed` only when the index changed — which made the documented -1
12
- // unreachable. This suite is that comparison.
4
+ // The suite exists because the two renderers used to carry independent,
5
+ // differently-wrong copies of the carousel's arithmetic with nothing comparing them;
6
+ // each rule that drifted is pinned by a test below.
13
7
  //
14
8
  // Scrolls are made instant by overriding the track's `scroll-behavior`; the
15
9
  // element sets it as an inline style precisely so it stays overridable.
@@ -44,10 +38,9 @@ function card(label: string): HTMLElement {
44
38
  }
45
39
 
46
40
  /**
47
- * Mount a carousel with the given page labels.
48
- *
49
- * The host gets an explicit size because the page pitch is MEASURED — a carousel
50
- * with no width has no distance, and every scroll would be a no-op.
41
+ * Mount a carousel with the given page labels. The host gets an explicit size because
42
+ * the page pitch is MEASURED: a carousel with no width has no distance, so every scroll
43
+ * would be a no-op.
51
44
  */
52
45
  function mount(labels: readonly string[], attributes: Record<string, string> = {}) {
53
46
  const host = document.createElement('div');
@@ -61,10 +54,9 @@ function mount(labels: readonly string[], attributes: Record<string, string> = {
61
54
  host.appendChild(carousel);
62
55
 
63
56
  const track = carousel.querySelector('.adw-carousel-track') as HTMLElement;
64
- // Instant scrolls: the assertions are about WHERE it lands, never about the
65
- // animation getting there. Snapping is off for the same reason — `mandatory`
66
- // re-snaps a programmatic `scrollLeft` to the nearest page, which is exactly
67
- // the fractional position several of these tests are about.
57
+ // Instant scrolls: the assertions are about WHERE it lands, never the animation.
58
+ // Snapping is off for the same reason — `mandatory` re-snaps a programmatic
59
+ // `scrollLeft` to the nearest page, the very fractional position under test.
68
60
  track.style.setProperty('scroll-behavior', 'auto');
69
61
  track.style.setProperty('scroll-snap-type', 'none');
70
62
  return { carousel, host, track };
@@ -131,16 +123,15 @@ function applyPageOp(carousel: CarouselElement, op: CarouselPageOp, pages: Map<s
131
123
  case 'scrollTo':
132
124
  case 'setPosition':
133
125
  case 'settle':
134
- // Position ops need a settled scroll, so they are driven separately
135
- // by the scroll tests below rather than through this replay.
126
+ // Position ops need a settled scroll and are driven by the scroll tests
127
+ // below, not through this replay.
136
128
  return true;
137
129
  }
138
130
  }
139
131
 
140
132
  export const AdwCarouselTest = async () => {
141
133
  await describe('adw-carousel page list (libadwaita conformance vectors)', async () => {
142
- // Only the structural rows: the element's page methods are what the DOM
143
- // exposes, while position ops need a real scroll and are covered below.
134
+ // Only the structural rows: the page methods are what the DOM exposes.
144
135
  const structural = CAROUSEL_PAGE_LIST_VECTORS.filter((vector) =>
145
136
  vector.ops.every((op) => op.kind === 'insert' || op.kind === 'remove' || op.kind === 'reorder'),
146
137
  );
@@ -157,11 +148,11 @@ export const AdwCarouselTest = async () => {
157
148
 
158
149
  expect(results).toStrictEqual([...vector.opResults]);
159
150
  expect(carousel.nPages).toBe(vector.expected.nPages);
160
- // The DOM order IS the model order — a reorder that only moved
161
- // the model would leave the pages painted where they were.
151
+ // The DOM order IS the model order: a reorder that moved only the model
152
+ // would leave the pages painted where they were.
162
153
  expect(domOrder(carousel)).toStrictEqual([...vector.expected.ids]);
163
- // And the position compensation reached the scroll container: a
164
- // page revealed before the current one must not push it aside.
154
+ // The position compensation reaches the scroll container: a page revealed
155
+ // before the current one must not push it aside.
165
156
  expect(carousel.position).toBe(vector.expected.position);
166
157
  host.remove();
167
158
  });
@@ -170,17 +161,15 @@ export const AdwCarouselTest = async () => {
170
161
 
171
162
  await describe('adw-carousel page lookup (libadwaita conformance vectors)', async () => {
172
163
  for (const vector of CAROUSEL_PAGE_AT_POSITION_VECTORS) {
173
- // Only the rows whose snap points are a settled page list are
174
- // reachable through the DOM; the mid-reveal geometry rows need a
175
- // reveal ramp this port does not have.
164
+ // Only rows whose snap points are a settled page list are reachable through
165
+ // the DOM; mid-reveal geometry rows need a reveal ramp this port lacks.
176
166
  const settled = vector.snapPoints.length > 0 && vector.snapPoints.every((point, i) => point === i);
177
167
  if (!settled) continue;
178
168
 
179
169
  await it(`${vector.position} → page ${vector.page} — ${vector.rule}`, async () => {
180
- // Driven through the `position` property rather than a real
181
- // scroll: a scroll offset is quantised to device pixels, so the
182
- // ±1e-7 rows around the tie are not expressible as a drag. The
183
- // tie ITSELF is, and gets its own drag test below.
170
+ // Driven through the `position` property, not a real scroll: a scroll
171
+ // offset is quantised to device pixels, so the ±1e-7 rows around the tie
172
+ // are not expressible as a drag. The tie itself is, below.
184
173
  const { carousel, host } = mount(vector.snapPoints.map((_point, index) => `p${index}`));
185
174
  (carousel as unknown as { position: number }).position = vector.position;
186
175
  await flush();
@@ -192,8 +181,8 @@ export const AdwCarouselTest = async () => {
192
181
 
193
182
  await describe('adw-carousel scroll wheel (scroll_cb)', async () => {
194
183
  await it('pages on a mouse wheel WITHOUT being asked to — the default is TRUE', async () => {
195
- // The regression pin: this element used to require an explicit
196
- // `allow-scroll-wheel` attribute, inverting adw-carousel.c:1103-1106.
184
+ // This element used to require an explicit `allow-scroll-wheel` attribute,
185
+ // inverting the libadwaita default.
197
186
  const { carousel, host } = mount(['a', 'b', 'c']);
198
187
  expect(carousel.allowScrollWheel).toBe(true);
199
188
  expect(wheel(carousel, { deltaY: 120 })).toBe(true);
@@ -212,8 +201,8 @@ export const AdwCarouselTest = async () => {
212
201
  });
213
202
 
214
203
  await it('pages on a horizontal delta — the fallback the port dropped', async () => {
215
- // adw-carousel.c:554-559. The old code returned early whenever
216
- // |deltaY| <= |deltaX|, so a dx-only event did nothing at all.
204
+ // The old code returned early whenever |deltaY| <= |deltaX|, so a dx-only
205
+ // event did nothing at all.
217
206
  const { carousel, host } = mount(['a', 'b', 'c']);
218
207
  expect(wheel(carousel, { deltaX: 120 })).toBe(true);
219
208
  await flush();
@@ -222,8 +211,8 @@ export const AdwCarouselTest = async () => {
222
211
  });
223
212
 
224
213
  await it('locks out a second notch for 150 ms', async () => {
225
- // adw-carousel.c:22, :526. The old accumulator had no time component
226
- // at all, so a fast wheel paged as many times as it had deltas.
214
+ // The old accumulator had no time component, so a fast wheel paged as many
215
+ // times as it had deltas.
227
216
  const { carousel, host } = mount(['a', 'b', 'c']);
228
217
  expect(wheel(carousel, { deltaY: 120 })).toBe(true);
229
218
  expect(wheel(carousel, { deltaY: 120 })).toBe(false);
@@ -233,8 +222,8 @@ export const AdwCarouselTest = async () => {
233
222
  });
234
223
 
235
224
  await it('leaves a touchpad gesture to the native scroll-snap', async () => {
236
- // A touchpad is ignored outright (:537-538) because its own kinetic
237
- // scroll already drives the strip — here, the track's scroll-snap.
225
+ // A touchpad is ignored outright: its own kinetic scroll already drives the
226
+ // strip, here the track's scroll-snap.
238
227
  const { carousel, host } = mount(['a', 'b', 'c']);
239
228
  expect(wheel(carousel, { deltaX: 12, deltaY: 7 })).toBe(false);
240
229
  await flush();
@@ -243,8 +232,8 @@ export const AdwCarouselTest = async () => {
243
232
  });
244
233
 
245
234
  await it('does not consume a wheel event it did not act on', async () => {
246
- // GDK_EVENT_STOP only when it paged (:561-562 vs :576). The old code
247
- // called preventDefault BEFORE its threshold test.
235
+ // GDK_EVENT_STOP only when it paged; the old code called preventDefault()
236
+ // BEFORE its threshold test.
248
237
  const { carousel, host } = mount(['a', 'b', 'c'], { interactive: 'false' });
249
238
  expect(wheel(carousel, { deltaY: 120 })).toBe(false);
250
239
  host.remove();
@@ -253,8 +242,8 @@ export const AdwCarouselTest = async () => {
253
242
 
254
243
  await describe('adw-carousel page-changed (scroll_animation_done_cb)', async () => {
255
244
  await it('reports every completed scroll, including back onto the same page', async () => {
256
- // The old emission was gated on the ROUNDED index changing, so a
257
- // settle back onto the page it started from said nothing.
245
+ // Gating the emission on the ROUNDED index means a settle back onto the page
246
+ // it started from says nothing.
258
247
  const { carousel, host } = mount(['a', 'b', 'c']);
259
248
  const seen: number[] = [];
260
249
  carousel.addEventListener('page-changed', (event) => {
@@ -271,8 +260,7 @@ export const AdwCarouselTest = async () => {
271
260
  await it('reports the LOWER page for a half-way settle', async () => {
272
261
  const { carousel, host } = mount(['a', 'b']);
273
262
  await dragTo(carousel, 0.5);
274
- // Math.round would have said page 1 here; every libadwaita lookup
275
- // resolves the tie downwards (adw-carousel.c:198-201).
263
+ // Math.round would say page 1; every libadwaita lookup resolves the tie down.
276
264
  expect(carousel.currentPage).toBe(0);
277
265
  host.remove();
278
266
  });
@@ -280,8 +268,8 @@ export const AdwCarouselTest = async () => {
280
268
 
281
269
  await describe('adw-carousel declarative position', async () => {
282
270
  await it('honours <adw-carousel position="2"> at upgrade time', async () => {
283
- // Attribute changes are delivered BEFORE connectedCallback, and the
284
- // old guard dropped them, so a declared position never applied.
271
+ // Attribute changes are delivered BEFORE connectedCallback, so a guard that
272
+ // drops them drops a declared position.
285
273
  const { carousel, host } = mount(['a', 'b', 'c'], { position: '2' });
286
274
  await flush();
287
275
  expect(carousel.currentPage).toBe(2);
@@ -360,9 +348,8 @@ export const AdwCarouselTest = async () => {
360
348
 
361
349
  await it('turns allow-long-swipes into scroll-snap-stop', async () => {
362
350
  // The property used to be inert: both branches of its ternary were
363
- // `current + step`. FALSE means "each swipe can only move to the
364
- // adjacent pages" (adw-carousel.c:1111-1113), which is what
365
- // scroll-snap-stop: always enforces.
351
+ // `current + step`. FALSE means "each swipe moves only to an adjacent
352
+ // page", which `scroll-snap-stop: always` enforces.
366
353
  const { carousel, host } = mount(['a', 'b', 'c']);
367
354
  const slot = carousel.querySelector('.adw-carousel-page') as HTMLElement;
368
355
  expect(carousel.allowLongSwipes).toBe(false);
@@ -374,8 +361,8 @@ export const AdwCarouselTest = async () => {
374
361
  });
375
362
 
376
363
  await it('spaces the pages by the spacing property', async () => {
377
- // `distance = size + spacing` (adw-carousel.c:767); with no gap in the
378
- // track the property would have nowhere to show up.
364
+ // `distance = size + spacing`; with no gap in the track the property would
365
+ // have nowhere to show up.
379
366
  const { carousel, host, track } = mount(['a', 'b', 'c'], { spacing: '16' });
380
367
  expect(track.style.columnGap).toBe('16px');
381
368
  // The pitch a drag is measured in must include the gap; if it did not,