@gjsify/adwaita-web 0.35.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.
- package/dist/adwaita-web.css +433 -33
- package/dist/adwaita-web.css.map +1 -1
- package/lib/types/adw-shortcut-label.spec.d.ts +2 -0
- package/lib/types/breakpoints.d.ts +10 -12
- package/lib/types/elements/adw-about-dialog.d.ts +19 -36
- package/lib/types/elements/adw-action-row.d.ts +9 -12
- package/lib/types/elements/adw-alert-dialog.d.ts +0 -12
- package/lib/types/elements/adw-avatar.d.ts +1 -0
- package/lib/types/elements/adw-bottom-sheet.d.ts +7 -12
- package/lib/types/elements/adw-button-content.d.ts +3 -4
- package/lib/types/elements/adw-button-row.d.ts +1 -1
- package/lib/types/elements/adw-carousel.d.ts +28 -49
- package/lib/types/elements/adw-checks.d.ts +2 -8
- package/lib/types/elements/adw-combo-row.d.ts +6 -6
- package/lib/types/elements/adw-data-grid.d.ts +5 -10
- package/lib/types/elements/adw-drop-down.d.ts +3 -11
- package/lib/types/elements/adw-entry-row.d.ts +20 -23
- package/lib/types/elements/adw-icon.d.ts +2 -4
- package/lib/types/elements/adw-inline-view-switcher.d.ts +18 -31
- package/lib/types/elements/adw-navigation-split-view.d.ts +7 -12
- package/lib/types/elements/adw-navigation-view.d.ts +2 -10
- package/lib/types/elements/adw-overlay-split-view.d.ts +28 -52
- package/lib/types/elements/adw-password-entry-row.d.ts +4 -5
- package/lib/types/elements/adw-popover.d.ts +2 -7
- package/lib/types/elements/adw-preferences-dialog.d.ts +21 -34
- package/lib/types/elements/adw-preferences-group.d.ts +12 -14
- package/lib/types/elements/adw-progress-bar.d.ts +3 -4
- package/lib/types/elements/adw-shortcut-label.d.ts +20 -0
- package/lib/types/elements/adw-sidebar.d.ts +5 -10
- package/lib/types/elements/adw-spinner.d.ts +3 -5
- package/lib/types/elements/adw-split-button.d.ts +6 -16
- package/lib/types/elements/adw-switch-row.d.ts +3 -3
- package/lib/types/elements/adw-tab-view.d.ts +16 -26
- package/lib/types/elements/adw-toast-overlay.d.ts +8 -13
- package/lib/types/elements/adw-view-stack.d.ts +7 -17
- package/lib/types/elements/adw-view-switcher-bar.d.ts +4 -8
- package/lib/types/elements/adw-view-switcher.d.ts +18 -38
- package/lib/types/elements/adw-wrap-box.d.ts +7 -17
- package/lib/types/elements/view-switcher-dom.d.ts +10 -13
- package/lib/types/index.d.ts +1 -0
- package/lib/types/source-view/hex.d.ts +7 -13
- package/lib/types/style-classes.spec.d.ts +11 -0
- package/lib/types/styles.generated.d.ts +1 -1
- package/package.json +6 -6
- package/scss/_about_dialog.scss +3 -2
- package/scss/_action_row.scss +33 -0
- package/scss/_avatar.scss +12 -0
- package/scss/_button.scss +44 -22
- package/scss/_card.scss +11 -8
- package/scss/_checks.scss +2 -4
- package/scss/_dialog.scss +3 -2
- package/scss/_headerbar.scss +23 -0
- package/scss/_popover.scss +8 -4
- package/scss/_preferences.scss +7 -7
- package/scss/_progress_bar.scss +9 -6
- package/scss/_reset.scss +1 -1
- package/scss/_shortcut_label.scss +80 -0
- package/scss/_sidebar.scss +57 -0
- package/scss/_status_page.scss +20 -0
- package/scss/_style_classes.scss +407 -0
- package/scss/_switch.scss +4 -3
- package/scss/_variables.scss +25 -0
- package/scss/adwaita-skin.scss +12 -0
- package/src/adw-about-dialog.spec.ts +34 -49
- package/src/adw-action-rows.spec.ts +25 -33
- package/src/adw-avatar.spec.ts +51 -20
- package/src/adw-banner.spec.ts +5 -8
- package/src/adw-button-content.spec.ts +9 -12
- package/src/adw-button-row.spec.ts +2 -4
- package/src/adw-carousel.spec.ts +40 -53
- package/src/adw-checks.spec.ts +36 -47
- package/src/adw-drop-down.spec.ts +25 -32
- package/src/adw-header-bar.spec.ts +5 -7
- package/src/adw-icon.spec.ts +23 -32
- package/src/adw-progress-bar.spec.ts +10 -12
- package/src/adw-row-state.spec.ts +9 -10
- package/src/adw-shortcut-label.spec.ts +112 -0
- package/src/adw-sidebar.spec.ts +17 -24
- package/src/adw-switch.spec.ts +11 -17
- package/src/adw-tab-view.spec.ts +7 -10
- package/src/adw-toast-overlay.spec.ts +7 -11
- package/src/adw-view-switcher-bar.spec.ts +10 -14
- package/src/adw-wrap-box.spec.ts +25 -36
- package/src/bottom-sheet.spec.ts +30 -36
- package/src/breakpoints.ts +15 -18
- package/src/chrome.spec.ts +13 -21
- package/src/elements/adw-about-dialog.ts +71 -114
- package/src/elements/adw-action-row.ts +22 -37
- package/src/elements/adw-alert-dialog.ts +25 -50
- package/src/elements/adw-avatar.ts +40 -15
- package/src/elements/adw-banner.ts +14 -18
- package/src/elements/adw-bottom-sheet.ts +31 -52
- package/src/elements/adw-button-content.ts +30 -37
- package/src/elements/adw-button-row.ts +14 -24
- package/src/elements/adw-carousel.ts +87 -148
- package/src/elements/adw-checks.ts +43 -56
- package/src/elements/adw-clamp.ts +8 -9
- package/src/elements/adw-combo-row.ts +17 -25
- package/src/elements/adw-data-grid.ts +26 -38
- package/src/elements/adw-dialog.ts +25 -56
- package/src/elements/adw-drop-down.ts +31 -51
- package/src/elements/adw-entry-row.ts +39 -46
- package/src/elements/adw-expander-row.ts +30 -53
- package/src/elements/adw-icon.ts +30 -53
- package/src/elements/adw-inline-view-switcher.ts +38 -67
- package/src/elements/adw-menu-button.ts +28 -58
- package/src/elements/adw-navigation-split-view.ts +35 -47
- package/src/elements/adw-navigation-view.ts +12 -28
- package/src/elements/adw-overlay-split-view.ts +100 -175
- package/src/elements/adw-password-entry-row.ts +18 -28
- package/src/elements/adw-popover.ts +28 -44
- package/src/elements/adw-preferences-dialog.ts +41 -66
- package/src/elements/adw-preferences-group.ts +33 -46
- package/src/elements/adw-progress-bar.ts +27 -47
- package/src/elements/adw-shortcut-label.ts +138 -0
- package/src/elements/adw-sidebar.ts +31 -39
- package/src/elements/adw-spin-row.ts +7 -14
- package/src/elements/adw-spinner.ts +31 -43
- package/src/elements/adw-split-button.ts +45 -75
- package/src/elements/adw-switch-row.ts +29 -46
- package/src/elements/adw-switch.ts +28 -47
- package/src/elements/adw-tab-view.ts +62 -103
- package/src/elements/adw-toast-overlay.ts +28 -35
- package/src/elements/adw-toggle-group.ts +9 -16
- package/src/elements/adw-view-stack.ts +32 -51
- package/src/elements/adw-view-switcher-bar.ts +26 -50
- package/src/elements/adw-view-switcher.ts +60 -110
- package/src/elements/adw-window-title.ts +4 -4
- package/src/elements/adw-wrap-box.ts +33 -59
- package/src/elements/view-switcher-dom.ts +15 -22
- package/src/entry-rows.spec.ts +18 -24
- package/src/index.ts +13 -15
- package/src/navigation-view.spec.ts +7 -10
- package/src/preferences-search.ts +11 -15
- package/src/preferences.spec.ts +13 -15
- package/src/source-view/adw-source-view.ts +6 -19
- package/src/source-view/hex.ts +7 -13
- package/src/split-button.spec.ts +32 -44
- package/src/split-views.spec.ts +83 -149
- package/src/style-classes.spec.ts +181 -0
- package/src/style-isolation.spec.ts +3 -6
- package/src/styles.generated.ts +1 -1
- package/src/tab-view.spec.ts +26 -35
- package/src/test.browser.mts +4 -0
- package/src/view-stack.spec.ts +23 -34
- 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
|
-
//
|
|
7
|
-
// -
|
|
8
|
-
//
|
|
9
|
-
//
|
|
10
|
-
//
|
|
11
|
-
//
|
|
12
|
-
//
|
|
13
|
-
//
|
|
14
|
-
//
|
|
15
|
-
//
|
|
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
|
|
39
|
-
*
|
|
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
|
-
//
|
|
149
|
-
//
|
|
150
|
-
//
|
|
151
|
-
//
|
|
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,
|
|
187
|
-
//
|
|
188
|
-
//
|
|
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
|
|
261
|
-
//
|
|
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
|
|
267
|
-
//
|
|
268
|
-
//
|
|
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,
|
package/src/adw-avatar.spec.ts
CHANGED
|
@@ -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
|
-
//
|
|
5
|
-
//
|
|
6
|
-
//
|
|
7
|
-
//
|
|
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
|
-
//
|
|
144
|
-
//
|
|
145
|
-
|
|
146
|
-
|
|
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
|
-
|
|
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
|
|
158
|
-
//
|
|
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('
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
//
|
|
172
|
-
|
|
173
|
-
|
|
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
|
};
|
package/src/adw-banner.spec.ts
CHANGED
|
@@ -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
|
|
5
|
-
//
|
|
6
|
-
//
|
|
7
|
-
//
|
|
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
|
|
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
|
-
//
|
|
6
|
-
// `image-text-button` on the button hosting it
|
|
7
|
-
//
|
|
8
|
-
//
|
|
9
|
-
//
|
|
10
|
-
//
|
|
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`
|
|
97
|
-
//
|
|
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
|
|
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
|
|
49
|
-
// "AdwButtonRow is always activatable."
|
|
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
|
package/src/adw-carousel.spec.ts
CHANGED
|
@@ -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,
|
|
5
|
-
// carousel's arithmetic
|
|
6
|
-
//
|
|
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
|
-
*
|
|
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
|
|
65
|
-
//
|
|
66
|
-
//
|
|
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
|
|
135
|
-
//
|
|
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
|
|
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
|
|
161
|
-
//
|
|
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
|
-
//
|
|
164
|
-
//
|
|
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
|
|
174
|
-
//
|
|
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
|
|
181
|
-
//
|
|
182
|
-
//
|
|
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
|
-
//
|
|
196
|
-
//
|
|
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
|
-
//
|
|
216
|
-
//
|
|
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
|
-
//
|
|
226
|
-
//
|
|
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
|
|
237
|
-
//
|
|
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
|
|
247
|
-
//
|
|
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
|
-
//
|
|
257
|
-
//
|
|
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
|
|
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,
|
|
284
|
-
//
|
|
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
|
|
364
|
-
//
|
|
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
|
|
378
|
-
//
|
|
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,
|