@gjsify/adwaita-web 0.36.0 → 0.38.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 +504 -34
- package/dist/adwaita-web.css.map +1 -1
- package/lib/types/accent.d.ts +28 -0
- package/lib/types/adw-accent.spec.d.ts +2 -0
- 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 +3 -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 +18 -7
- 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/_theme.scss +20 -2
- package/scss/_variables.scss +34 -1
- package/scss/adwaita-skin.scss +12 -0
- package/src/accent.ts +74 -0
- package/src/adw-about-dialog.spec.ts +34 -49
- package/src/adw-accent.spec.ts +105 -0
- 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 +24 -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 +6 -0
- package/src/view-stack.spec.ts +23 -34
- package/src/view-switcher.spec.ts +56 -84
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,
|
package/src/adw-checks.spec.ts
CHANGED
|
@@ -1,17 +1,15 @@
|
|
|
1
1
|
// DOM-level tests for <adw-checkbox> and <adw-radio>, plus the browser side of
|
|
2
2
|
// the `RADIO_GROUP_VECTORS` conformance table.
|
|
3
3
|
//
|
|
4
|
-
//
|
|
5
|
-
//
|
|
6
|
-
//
|
|
7
|
-
//
|
|
8
|
-
//
|
|
9
|
-
// below reads back the HOST attribute of every member for exactly that reason.
|
|
4
|
+
// The browser's native radio exclusivity is NOT enough: `<input type="radio" name="g">`
|
|
5
|
+
// unchecks its sibling INPUT and stops there, while the sibling `<adw-radio>` HOST keeps
|
|
6
|
+
// its `checked` attribute — the element's published state and the selector
|
|
7
|
+
// `_checks.scss` paints from — so a group left to the browser draws two selected radios.
|
|
8
|
+
// Every vector below reads back the HOST attribute of every member for that reason.
|
|
10
9
|
//
|
|
11
|
-
// The vectors run under a per-scenario group-name prefix
|
|
12
|
-
// document-global by design (as a bare `<input type=radio name=x>` outside a
|
|
13
|
-
//
|
|
14
|
-
// selection. The prefix is the isolation, not a change to what is being tested.
|
|
10
|
+
// The vectors run under a per-scenario group-name prefix because `RadioGroupState` is
|
|
11
|
+
// document-global by design (as a bare `<input type=radio name=x>` outside a form is),
|
|
12
|
+
// so two scenarios sharing the literal name `colour` would share a selection.
|
|
15
13
|
|
|
16
14
|
import { describe, expect, it } from '@gjsify/unit';
|
|
17
15
|
import { RADIO_GROUP_VECTORS } from '@gjsify/adwaita-core/conformance';
|
|
@@ -60,12 +58,11 @@ export const AdwChecksTest = async () => {
|
|
|
60
58
|
expect(input).toBeTruthy();
|
|
61
59
|
expect(input.type).toBe('checkbox');
|
|
62
60
|
expect(indicator).toBeTruthy();
|
|
63
|
-
// The focus ring is `input:focus-visible ~ .adw-check-indicator`, so
|
|
64
|
-
//
|
|
61
|
+
// The focus ring is `input:focus-visible ~ .adw-check-indicator`, so the
|
|
62
|
+
// input MUST precede the indicator.
|
|
65
63
|
expect(input.nextElementSibling).toBe(indicator);
|
|
66
64
|
expect(el.checked).toBe(false);
|
|
67
65
|
expect(input.getAttribute('aria-checked')).toBe('false');
|
|
68
|
-
// An empty label takes no space.
|
|
69
66
|
expect(label.hidden).toBe(true);
|
|
70
67
|
parent.remove();
|
|
71
68
|
});
|
|
@@ -110,9 +107,8 @@ export const AdwChecksTest = async () => {
|
|
|
110
107
|
});
|
|
111
108
|
|
|
112
109
|
await it('a click on the hidden input toggles exactly once', () => {
|
|
113
|
-
// The input is 0x0 so a pointer never reaches it, but keyboard
|
|
114
|
-
//
|
|
115
|
-
// toggle a second time on the way out.
|
|
110
|
+
// The input is 0x0 so a pointer never reaches it, but keyboard activation and
|
|
111
|
+
// `input.click()` do — and the host listener must not toggle a second time.
|
|
116
112
|
const { el, parent } = mountCheckbox();
|
|
117
113
|
const events = record(el, 'notify::checked');
|
|
118
114
|
parts(el).input.click();
|
|
@@ -143,18 +139,17 @@ export const AdwChecksTest = async () => {
|
|
|
143
139
|
el.checked = true;
|
|
144
140
|
el.indeterminate = true;
|
|
145
141
|
const { input } = parts(el);
|
|
146
|
-
// HTML has a native mixed CHECKBOX,
|
|
147
|
-
//
|
|
142
|
+
// HTML has a native mixed CHECKBOX; the radio has none, so an explicit
|
|
143
|
+
// aria-checked makes it read the same way.
|
|
148
144
|
expect(input.indeterminate).toBe(true);
|
|
149
145
|
expect(input.getAttribute('aria-checked')).toBe('mixed');
|
|
150
146
|
parent.remove();
|
|
151
147
|
});
|
|
152
148
|
|
|
153
149
|
await it('draws the dash even when also checked', () => {
|
|
154
|
-
// `
|
|
155
|
-
//
|
|
156
|
-
//
|
|
157
|
-
// against each other proves the cascade, not just that a mask exists.
|
|
150
|
+
// In `_checks.scss` the `:indeterminate` glyph is re-declared AFTER both
|
|
151
|
+
// `:checked` rules at equal specificity, so the dash wins. Comparing all
|
|
152
|
+
// three states proves the cascade, not just that a mask exists.
|
|
158
153
|
const parent = host();
|
|
159
154
|
const make = (attrs: string[]) => {
|
|
160
155
|
const el = document.createElement('adw-checkbox') as AdwCheckbox;
|
|
@@ -173,7 +168,7 @@ export const AdwChecksTest = async () => {
|
|
|
173
168
|
|
|
174
169
|
await it('activating an indeterminate checkbox clears it and checks', () => {
|
|
175
170
|
// HTML's pre-click activation steps, not a reading of
|
|
176
|
-
// GtkCheckButton:inconsistent
|
|
171
|
+
// GtkCheckButton:inconsistent, which is not verifiable in this tree.
|
|
177
172
|
const { el, parent } = mountCheckbox();
|
|
178
173
|
el.indeterminate = true;
|
|
179
174
|
el.click();
|
|
@@ -192,33 +187,31 @@ export const AdwChecksTest = async () => {
|
|
|
192
187
|
|
|
193
188
|
for (const el of [check, radio]) {
|
|
194
189
|
const style = getComputedStyle(parts(el).indicator);
|
|
195
|
-
// GTK sizes the CONTENT box at 14px and grows the drawn control
|
|
196
|
-
//
|
|
197
|
-
// the ADR-0010 descendant border-box pin.
|
|
190
|
+
// GTK sizes the CONTENT box at 14px and grows the drawn control with
|
|
191
|
+
// `padding: 3px` — content-box, not the ADR-0010 border-box pin.
|
|
198
192
|
expect(style.width).toBe('14px');
|
|
199
193
|
expect(style.height).toBe('14px');
|
|
200
194
|
expect(style.paddingTop).toBe('3px');
|
|
201
195
|
}
|
|
202
196
|
|
|
203
|
-
// `check { border-radius: 6px }`
|
|
204
|
-
// `radio { border-radius: 100% }
|
|
197
|
+
// `check { border-radius: 6px }` vs
|
|
198
|
+
// `radio { border-radius: 100% }`.
|
|
205
199
|
expect(getComputedStyle(parts(check).indicator).borderTopLeftRadius).toBe('6px');
|
|
206
200
|
expect(getComputedStyle(parts(radio).indicator).borderTopLeftRadius).toContain('%');
|
|
207
201
|
parent.remove();
|
|
208
202
|
});
|
|
209
203
|
|
|
210
204
|
await it('a checked radio FILLS, with the SAME accent background as a checked check', () => {
|
|
211
|
-
// refs/adwaita-web's `_radio.scss
|
|
212
|
-
//
|
|
213
|
-
//
|
|
214
|
-
//
|
|
205
|
+
// refs/adwaita-web's `_radio.scss` colours only the border and the dot,
|
|
206
|
+
// leaving the indicator on the view background; libadwaita fills the whole
|
|
207
|
+
// control from the block it SHARES with the checkbox, and libadwaita wins —
|
|
208
|
+
// so the two fills must be identical.
|
|
215
209
|
//
|
|
216
|
-
// THREE elements rather than one flipped in place: the indicator
|
|
217
|
-
//
|
|
218
|
-
//
|
|
219
|
-
//
|
|
220
|
-
//
|
|
221
|
-
// cannot reach the reference radio.
|
|
210
|
+
// THREE elements rather than one flipped in place: the indicator TRANSITIONS
|
|
211
|
+
// `background`, so a computed read right after `checked = true` returns the
|
|
212
|
+
// INTERPOLATED colour. Declaring the state before insertion leaves no
|
|
213
|
+
// transition part-way through, and distinct names keep seeding one group from
|
|
214
|
+
// reaching the reference radio.
|
|
222
215
|
const parent = host();
|
|
223
216
|
const plain = document.createElement('adw-radio') as AdwRadio;
|
|
224
217
|
plain.setAttribute('name', 'fill-plain');
|
|
@@ -243,8 +236,6 @@ export const AdwChecksTest = async () => {
|
|
|
243
236
|
for (const [index, vector] of RADIO_GROUP_VECTORS.entries()) {
|
|
244
237
|
await it(`${vector.name} — ${vector.rule}`, () => {
|
|
245
238
|
const parent = host();
|
|
246
|
-
// Per-scenario prefix: the registry is document-global, so two
|
|
247
|
-
// scenarios sharing a literal group name would share a selection.
|
|
248
239
|
const scope = (name: string) => `v${index}-${name}`;
|
|
249
240
|
|
|
250
241
|
const members = vector.members.map(([name, value]) => {
|
|
@@ -272,9 +263,8 @@ export const AdwChecksTest = async () => {
|
|
|
272
263
|
expect(parts(el).input.checked).toBe(shouldHold);
|
|
273
264
|
}
|
|
274
265
|
|
|
275
|
-
// Every emitted change repaints the winner
|
|
276
|
-
//
|
|
277
|
-
// the group state exists.
|
|
266
|
+
// Every emitted change repaints the winner AND the loser when there was
|
|
267
|
+
// one; that second repaint is why the group state exists.
|
|
278
268
|
const expectedEvents = vector.emitted.reduce(
|
|
279
269
|
(total, change) => total + (change.deselected === null ? 1 : 2),
|
|
280
270
|
0,
|
|
@@ -327,10 +317,9 @@ export const AdwChecksTest = async () => {
|
|
|
327
317
|
});
|
|
328
318
|
|
|
329
319
|
await it('a NAMELESS radio is not in a group', () => {
|
|
330
|
-
// HTML forms a radio
|
|
331
|
-
//
|
|
332
|
-
//
|
|
333
|
-
// registry keyed by name does if nothing guards the empty key.
|
|
320
|
+
// HTML forms a radio group only from a non-empty `name`; without that rule
|
|
321
|
+
// every nameless radio lands in one `''` group and fights over it, which is
|
|
322
|
+
// what a registry keyed by name does unless it guards the empty key.
|
|
334
323
|
const parent = host();
|
|
335
324
|
const first = document.createElement('adw-radio') as AdwRadio;
|
|
336
325
|
const second = document.createElement('adw-radio') as AdwRadio;
|
|
@@ -1,17 +1,14 @@
|
|
|
1
|
-
// DOM-level behaviour tests for <adw-drop-down
|
|
2
|
-
// @gjsify/adwaita-web browser test axis.
|
|
3
|
-
// sync, notify-on-change semantics, popover open/close, item selection, search
|
|
4
|
-
// filtering and the disabled guard.
|
|
1
|
+
// DOM-level behaviour tests for <adw-drop-down>, in a real browser via the
|
|
2
|
+
// @gjsify/adwaita-web browser test axis.
|
|
5
3
|
//
|
|
6
|
-
// The SELECTION half is driven by the SAME table the NativeScript renderer
|
|
7
|
-
//
|
|
8
|
-
//
|
|
9
|
-
//
|
|
10
|
-
//
|
|
11
|
-
//
|
|
12
|
-
//
|
|
13
|
-
//
|
|
14
|
-
// the browser's driver for both.
|
|
4
|
+
// The SELECTION half is driven by the SAME table the NativeScript renderer asserts
|
|
5
|
+
// against (`COMBO_SELECTION_VECTORS`, `@gjsify/adwaita-core/conformance`), replayed
|
|
6
|
+
// through the API a consumer would use and read back as what a user would see: the
|
|
7
|
+
// button label, `selected`/`selectedValue` and the `change` stream. `<adw-combo-row>`
|
|
8
|
+
// composes the same `ComboState` but cannot drive the table — two of its four step ops
|
|
9
|
+
// have no DOM spelling, since its options come from the `items` attribute at connect
|
|
10
|
+
// time only (`items` is not observed) and it publishes no select-by-value setter. So
|
|
11
|
+
// this element is the browser's driver for both.
|
|
15
12
|
import { describe, it, expect } from '@gjsify/unit';
|
|
16
13
|
|
|
17
14
|
import { COMBO_SELECTION_VECTORS } from '@gjsify/adwaita-core/conformance';
|
|
@@ -53,9 +50,8 @@ function applyStep(dd: AdwDropDown, step: ComboSelectionStep): void {
|
|
|
53
50
|
case 'select':
|
|
54
51
|
openChooser(dd);
|
|
55
52
|
if (step.index < 0) {
|
|
56
|
-
// The dismissed chooser: the popover closes with nothing picked
|
|
57
|
-
//
|
|
58
|
-
// native sheet.
|
|
53
|
+
// The dismissed chooser: the popover closes with nothing picked — here an
|
|
54
|
+
// outside pointerdown rather than a cancelled native sheet.
|
|
59
55
|
document.dispatchEvent(new PointerEvent('pointerdown', { bubbles: true }));
|
|
60
56
|
return;
|
|
61
57
|
}
|
|
@@ -65,13 +61,12 @@ function applyStep(dd: AdwDropDown, step: ComboSelectionStep): void {
|
|
|
65
61
|
}
|
|
66
62
|
|
|
67
63
|
/**
|
|
68
|
-
* The ONE row this element answers differently,
|
|
69
|
-
*
|
|
70
|
-
*
|
|
71
|
-
*
|
|
72
|
-
*
|
|
73
|
-
*
|
|
74
|
-
* change silently.
|
|
64
|
+
* The ONE row this element answers differently, on purpose: its published `selected`
|
|
65
|
+
* contract REJECTS an out-of-range set (`_selectIndex` gates on `ComboState.hasIndex`)
|
|
66
|
+
* where the core state accepts it and reports an empty value — the split
|
|
67
|
+
* `ComboState.hasIndex`'s doc records and `<adw-combo-row>` takes the other side of. The
|
|
68
|
+
* row is still driven below, against the element's answer, so neither policy can change
|
|
69
|
+
* silently.
|
|
75
70
|
*/
|
|
76
71
|
const DROP_DOWN_REJECTS = 'an index past the end';
|
|
77
72
|
|
|
@@ -102,10 +97,9 @@ export const AdwDropDownTest = async () => {
|
|
|
102
97
|
expect(dd.selected).toBe(vector.selected);
|
|
103
98
|
expect(dd.selectedValue).toBe(vector.value);
|
|
104
99
|
expect(labelText(dd)).toBe(vector.label);
|
|
105
|
-
// `change` is this element's DOM spelling of the `interactive`
|
|
106
|
-
//
|
|
107
|
-
//
|
|
108
|
-
// events a listener sees.
|
|
100
|
+
// `change` is this element's DOM spelling of the `interactive` flag: it
|
|
101
|
+
// fires for a user pick and never for a programmatic set, so the
|
|
102
|
+
// interactive frames of the stream ARE the events a listener sees.
|
|
109
103
|
expect(changes).toStrictEqual(
|
|
110
104
|
vector.emitted
|
|
111
105
|
.filter((change) => change.interactive)
|
|
@@ -119,11 +113,10 @@ export const AdwDropDownTest = async () => {
|
|
|
119
113
|
const vector = vectorNamed(DROP_DOWN_REJECTS);
|
|
120
114
|
const { dd, changes } = mountRecording();
|
|
121
115
|
|
|
122
|
-
// Everything up to the out-of-range set…
|
|
123
116
|
for (const step of vector.steps.slice(0, -1)) applyStep(dd, step);
|
|
124
117
|
const before = { selected: dd.selected, value: dd.selectedValue, label: labelText(dd) };
|
|
125
|
-
//
|
|
126
|
-
//
|
|
118
|
+
// The out-of-range set itself, which the core state would accept and report
|
|
119
|
+
// as nothing-to-draw (`value`/`label` both empty below).
|
|
127
120
|
expect(vector.value).toBe('');
|
|
128
121
|
expect(vector.label).toBe('');
|
|
129
122
|
applyStep(dd, vector.steps[vector.steps.length - 1]);
|
|
@@ -210,8 +203,8 @@ export const AdwDropDownTest = async () => {
|
|
|
210
203
|
});
|
|
211
204
|
|
|
212
205
|
await it('setting options + selected before connect does not crash + applies on connect', async () => {
|
|
213
|
-
//
|
|
214
|
-
//
|
|
206
|
+
// The property setters can run BEFORE connectedCallback builds the DOM, where
|
|
207
|
+
// _updateLabel would touch an undefined label element.
|
|
215
208
|
const dd = document.createElement('adw-drop-down') as AdwDropDown;
|
|
216
209
|
dd.options = ['a', 'b', 'c'];
|
|
217
210
|
dd.selected = 2;
|
|
@@ -1,14 +1,12 @@
|
|
|
1
1
|
// DOM-level tests for <adw-header-bar>'s title.
|
|
2
2
|
//
|
|
3
|
-
// `title`
|
|
4
|
-
//
|
|
5
|
-
//
|
|
6
|
-
// with, and nothing in the build was in a position to notice: the attribute was
|
|
7
|
-
// still THERE, and the element still rendered A title.
|
|
3
|
+
// `title` must be OBSERVED, not read once at `connectedCallback`: a header bar tracking
|
|
4
|
+
// the open document keeps its construction-time title otherwise, and nothing notices
|
|
5
|
+
// because the attribute is still there and A title still renders.
|
|
8
6
|
//
|
|
9
7
|
// `Adw.HeaderBar` builds its centre from `title`/`subtitle` through a derived
|
|
10
|
-
// `AdwWindowTitle` bound to the properties, unless a `title-widget` replaces it
|
|
11
|
-
//
|
|
8
|
+
// `AdwWindowTitle` bound to the properties, unless a `title-widget` replaces it — the
|
|
9
|
+
// either/or the `slot="center"` case below asserts.
|
|
12
10
|
|
|
13
11
|
import { describe, expect, it } from '@gjsify/unit';
|
|
14
12
|
|
package/src/adw-icon.spec.ts
CHANGED
|
@@ -1,16 +1,11 @@
|
|
|
1
|
-
// DOM-level tests for <adw-icon>, driven by the SAME `normalizeIconName` vectors
|
|
2
|
-
//
|
|
3
|
-
// REGRESSION PROOF for the drift the element was extracted to close.
|
|
1
|
+
// DOM-level tests for <adw-icon>, driven by the SAME `normalizeIconName` vectors the
|
|
2
|
+
// core suite asserts against (`@gjsify/adwaita-core/conformance`).
|
|
4
3
|
//
|
|
5
|
-
// The
|
|
6
|
-
//
|
|
7
|
-
//
|
|
8
|
-
// `<adw-
|
|
9
|
-
//
|
|
10
|
-
// a stray `b` class through `<adw-menu-button>`, for its own icon AND for every
|
|
11
|
-
// JSON menu entry's, while the one element that guarded kept passing its spec.
|
|
12
|
-
// The vectors below now run against the ELEMENT, so a renderer that starts
|
|
13
|
-
// building the class by hand again fails naming the input.
|
|
4
|
+
// The icon-name class must never be built by hand: a name is only usable once it is
|
|
5
|
+
// known to be a SINGLE CSS token, and hand-built spans skipped that check, so
|
|
6
|
+
// `icon-name="a b"` interpolated a stray second class and shipped it through
|
|
7
|
+
// `<adw-menu-button>` for its own icon and for every JSON menu entry's. The vectors run
|
|
8
|
+
// against the ELEMENT, so hand-building the class again fails naming the input.
|
|
14
9
|
import { describe, expect, it } from '@gjsify/unit';
|
|
15
10
|
|
|
16
11
|
import { VIEW_STACK_ICON_NAME_VECTORS } from '@gjsify/adwaita-core/conformance';
|
|
@@ -39,13 +34,11 @@ export const AdwIconTest = async () => {
|
|
|
39
34
|
el.iconName = icon ?? null;
|
|
40
35
|
|
|
41
36
|
expect(el.resolvedIconName).toBe(normalized);
|
|
42
|
-
// The base class is unconditional
|
|
43
|
-
//
|
|
44
|
-
// omits.
|
|
37
|
+
// The base class is unconditional: a nameless icon is still an icon box,
|
|
38
|
+
// which `<adw-status-page>` hides rather than omits.
|
|
45
39
|
expect(el.classList.contains('adw-icon')).toBe(true);
|
|
46
|
-
// …and NOTHING else is on the node
|
|
47
|
-
//
|
|
48
|
-
// leave a stray `b` here.
|
|
40
|
+
// …and NOTHING else is on the node — the assertion a hand-rolled copy
|
|
41
|
+
// cannot make, where `icon-name="a b"` leaves a stray `b`.
|
|
49
42
|
expect(extraClasses(el)).toStrictEqual(normalized === '' ? [] : [`adw-icon--${normalized}`]);
|
|
50
43
|
host.remove();
|
|
51
44
|
});
|
|
@@ -66,8 +59,8 @@ export const AdwIconTest = async () => {
|
|
|
66
59
|
expect(extraClasses(el)).toStrictEqual(['adw-drop-down-arrow', 'adw-icon--go-down', 'start']);
|
|
67
60
|
|
|
68
61
|
el.iconName = 'go-next';
|
|
69
|
-
// Exactly one mask class at a time, and the caller's are untouched
|
|
70
|
-
//
|
|
62
|
+
// Exactly one mask class at a time, and the caller's are untouched: a
|
|
63
|
+
// `className =` assignment would take them with it.
|
|
71
64
|
expect(extraClasses(el)).toStrictEqual(['adw-drop-down-arrow', 'adw-icon--go-next', 'start']);
|
|
72
65
|
|
|
73
66
|
el.iconName = null;
|
|
@@ -86,10 +79,9 @@ export const AdwIconTest = async () => {
|
|
|
86
79
|
const styled = getComputedStyle(el);
|
|
87
80
|
expect(styled.width).toBe('32px');
|
|
88
81
|
expect(styled.height).toBe('32px');
|
|
89
|
-
// Sizing only the box
|
|
90
|
-
//
|
|
91
|
-
//
|
|
92
|
-
// omitted half (Firefox reports `32px auto`, in the inline style too).
|
|
82
|
+
// Sizing only the box leaves a 16px glyph floating in it. Only the axis set is
|
|
83
|
+
// asserted: `mask-size: 32px` means "32px wide, height from the aspect ratio",
|
|
84
|
+
// and the CSSOM re-serializes the omitted half (Firefox reports `32px auto`).
|
|
93
85
|
expect(styled.maskSize.split(' ')[0]).toBe('32px');
|
|
94
86
|
expect(styled.webkitMaskSize.split(' ')[0]).toBe('32px');
|
|
95
87
|
|
|
@@ -99,10 +91,10 @@ export const AdwIconTest = async () => {
|
|
|
99
91
|
});
|
|
100
92
|
|
|
101
93
|
await it('paints in the INHERITED colour, not the boundary reset colour', () => {
|
|
102
|
-
// `.adw-icon` masks with `currentColor
|
|
103
|
-
//
|
|
104
|
-
//
|
|
105
|
-
//
|
|
94
|
+
// `.adw-icon` masks with `currentColor` and <adw-icon> is a custom element, so
|
|
95
|
+
// the ADR-0010 `$adw-components` reset re-roots `color` on it: without
|
|
96
|
+
// `color: inherit` in `_icon.scss` every icon repaints in `--window-fg-color`
|
|
97
|
+
// regardless of the button it sits in.
|
|
106
98
|
const { el: box, host } = mount<HTMLElement>('div');
|
|
107
99
|
box.style.color = 'rgb(255, 0, 0)';
|
|
108
100
|
const icon = document.createElement('adw-icon') as AdwIcon;
|
|
@@ -142,10 +134,9 @@ export const AdwIconTest = async () => {
|
|
|
142
134
|
});
|
|
143
135
|
|
|
144
136
|
await it('every icon in the package is an <adw-icon>', () => {
|
|
145
|
-
//
|
|
146
|
-
// the
|
|
147
|
-
//
|
|
148
|
-
// here even if it happens to guard its own name correctly.
|
|
137
|
+
// Asserted structurally: a page of every icon-drawing widget, and no bare span
|
|
138
|
+
// carrying the base class — so a new hand-rolled `<span class="adw-icon …">`
|
|
139
|
+
// fails here even when it guards its own name correctly.
|
|
149
140
|
const { el: page, host } = mount<HTMLElement>('div');
|
|
150
141
|
page.innerHTML = `
|
|
151
142
|
<adw-button icon="go-next" label="Next"></adw-button>
|