@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
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>
|
|
@@ -1,13 +1,11 @@
|
|
|
1
1
|
// DOM-level tests for <adw-progress-bar>.
|
|
2
2
|
//
|
|
3
|
-
// Three things
|
|
4
|
-
//
|
|
5
|
-
//
|
|
6
|
-
//
|
|
7
|
-
//
|
|
8
|
-
//
|
|
9
|
-
// exist, and a `@extend` that silently reached nothing would look identical in
|
|
10
|
-
// the source.
|
|
3
|
+
// Three things matter more than the attribute round-trips: the CLAMP is GLib's
|
|
4
|
+
// (`glibClamp` from `@gjsify/adwaita-core`, not a local `Math.min/max`); a ZERO fraction
|
|
5
|
+
// makes the indicator VANISH rather than draw a rounded stub
|
|
6
|
+
// (`> trough.empty > progress { all: unset }` in `_progress-bar.scss`); and the text node
|
|
7
|
+
// really PICKS UP `.dimmed` + `.numeric` from `_labels.scss` — a `@extend` that reached
|
|
8
|
+
// nothing would look identical in the source.
|
|
11
9
|
|
|
12
10
|
import { describe, expect, it } from '@gjsify/unit';
|
|
13
11
|
|
|
@@ -124,8 +122,8 @@ export const AdwProgressBarTest = async () => {
|
|
|
124
122
|
});
|
|
125
123
|
|
|
126
124
|
await it('the text node composes .dimmed and .numeric from _labels.scss', () => {
|
|
127
|
-
// `> text { @extend
|
|
128
|
-
// (_progress-bar.scss
|
|
125
|
+
// `> text { @extend.dimmed; @extend.numeric; }`
|
|
126
|
+
// (_progress-bar.scss) — this asserts the extends actually
|
|
129
127
|
// REACHED the utility classes, which a source read cannot tell.
|
|
130
128
|
const { el, host } = mount();
|
|
131
129
|
el.showText = true;
|
|
@@ -138,8 +136,8 @@ export const AdwProgressBarTest = async () => {
|
|
|
138
136
|
|
|
139
137
|
await describe('<adw-progress-bar> variants', async () => {
|
|
140
138
|
await it('is an 8px trough by default and a 2px hairline with osd', () => {
|
|
141
|
-
// The 8px is libadwaita's (_progress-bar.scss
|
|
142
|
-
// `.osd` variant
|
|
139
|
+
// The 8px is libadwaita's (_progress-bar.scss) and the 2px is its
|
|
140
|
+
// `.osd` variant — NOT the vendored web port's 6px, which
|
|
143
141
|
// has no OSD variant at all.
|
|
144
142
|
//
|
|
145
143
|
// TWO bars rather than one flipped in place, on purpose: the trough
|
|
@@ -85,15 +85,14 @@ export const AdwRowStateTest = async () => {
|
|
|
85
85
|
const value = row.querySelector('.adw-row-value') as HTMLSpanElement;
|
|
86
86
|
const select = row.querySelector('select') as HTMLSelectElement;
|
|
87
87
|
|
|
88
|
-
// The ARROW: `.adw-row-value::after` is masked in
|
|
89
|
-
// `[data-presents-chooser='false']` block removes it. Asserted
|
|
90
|
-
//
|
|
91
|
-
//
|
|
92
|
-
// its children's display.
|
|
88
|
+
// The ARROW: `.adw-row-value::after` is masked in and the
|
|
89
|
+
// `[data-presents-chooser='false']` block removes it. Asserted as "not
|
|
90
|
+
// none" rather than against the authored `inline-block`, because
|
|
91
|
+
// `.adw-row-value` is a flex container and blockifies its children.
|
|
93
92
|
expect(getComputedStyle(value, '::after').display === 'none').toBe(!presentsChooser);
|
|
94
|
-
//
|
|
95
|
-
//
|
|
96
|
-
//
|
|
93
|
+
// …and the ROW being activatable, which here is the overlaid <select>
|
|
94
|
+
// taking clicks at all: hiding the arrow alone leaves a row that still
|
|
95
|
+
// opens a one-entry popup.
|
|
97
96
|
expect(select.disabled).toBe(!presentsChooser);
|
|
98
97
|
|
|
99
98
|
host.remove();
|
|
@@ -108,8 +107,8 @@ export const AdwRowStateTest = async () => {
|
|
|
108
107
|
const select = row.querySelector('select') as HTMLSelectElement;
|
|
109
108
|
expect(select.disabled).toBe(true);
|
|
110
109
|
|
|
111
|
-
// `items` is read at connect only, so the model is replaced through
|
|
112
|
-
// the
|
|
110
|
+
// `items` is read at connect only, so the model is replaced through the state
|
|
111
|
+
// the element composes — the same path a consumer has.
|
|
113
112
|
(
|
|
114
113
|
row as unknown as { _state: { setOptions(o: { label: string; value: string }[]): void } }
|
|
115
114
|
)._state.setOptions([
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
// <adw-shortcut-label> against `SHORTCUT_LABEL_VECTORS` — the same table the
|
|
2
|
+
// core suite drives, so a divergence between the parse and what this element
|
|
3
|
+
// actually renders fails a test naming the accelerator.
|
|
4
|
+
//
|
|
5
|
+
// The serialiser below walks the REAL DOM and produces the table's compact form.
|
|
6
|
+
// That is the whole point of the shared format: if this file built its own
|
|
7
|
+
// expectation strings instead, the two suites could agree with each other while
|
|
8
|
+
// both disagreeing with libadwaita.
|
|
9
|
+
import { describe, expect, it } from '@gjsify/unit';
|
|
10
|
+
import { SHORTCUT_LABEL_VECTORS } from '@gjsify/adwaita-core/conformance';
|
|
11
|
+
|
|
12
|
+
import '@gjsify/adwaita-web';
|
|
13
|
+
|
|
14
|
+
/** The rendered element in the table's compact form (see conformance/shortcut-label.ts). */
|
|
15
|
+
const serialize = (host: HTMLElement): string =>
|
|
16
|
+
[...host.children]
|
|
17
|
+
.map((child) => {
|
|
18
|
+
if (child.classList.contains('adw-shortcut-label-disabled'))
|
|
19
|
+
return `(disabled: ${child.textContent ?? ''})`;
|
|
20
|
+
if (child.classList.contains('dimmed')) return child.textContent ?? '';
|
|
21
|
+
|
|
22
|
+
return [...child.children]
|
|
23
|
+
.map((cap) => {
|
|
24
|
+
const side = cap.querySelector('.adw-shortcut-label-side');
|
|
25
|
+
// The marker rides INSIDE the keycap, so its text has to come
|
|
26
|
+
// back out of the label before the two are compared.
|
|
27
|
+
const label = (cap.textContent ?? '').slice(
|
|
28
|
+
0,
|
|
29
|
+
(cap.textContent ?? '').length - (side?.textContent?.length ?? 0),
|
|
30
|
+
);
|
|
31
|
+
return `[${side ? `${label} (${side.textContent})` : label}]`;
|
|
32
|
+
})
|
|
33
|
+
.join('');
|
|
34
|
+
})
|
|
35
|
+
.join(' ')
|
|
36
|
+
.trim();
|
|
37
|
+
|
|
38
|
+
export const AdwShortcutLabelTest = async () => {
|
|
39
|
+
await describe('<adw-shortcut-label> (conformance vectors)', async () => {
|
|
40
|
+
for (const vector of SHORTCUT_LABEL_VECTORS) {
|
|
41
|
+
// The Apple glyph set is a build constant in the C and an option in
|
|
42
|
+
// core; the ELEMENT reads its platform from nothing, so the two
|
|
43
|
+
// Apple rows have no element to drive. They stay core-only.
|
|
44
|
+
if (vector.platform === 'apple') continue;
|
|
45
|
+
|
|
46
|
+
await it(`${vector.accelerator || '(empty)'} — ${vector.rule}`, async () => {
|
|
47
|
+
const host = document.createElement('adw-shortcut-label');
|
|
48
|
+
host.setAttribute('accelerator', vector.accelerator);
|
|
49
|
+
if (vector.disabledText !== undefined) host.setAttribute('disabled-text', vector.disabledText);
|
|
50
|
+
// RTL comes from the rendered direction, so the vector's
|
|
51
|
+
// direction has to be a real attribute on a CONNECTED element.
|
|
52
|
+
if (vector.direction === 'rtl') host.setAttribute('dir', 'rtl');
|
|
53
|
+
document.body.appendChild(host);
|
|
54
|
+
|
|
55
|
+
expect(serialize(host)).toBe(vector.expected);
|
|
56
|
+
if (vector.accessibleLabel !== undefined) {
|
|
57
|
+
expect(host.getAttribute('aria-label')).toBe(vector.accessibleLabel);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
host.remove();
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
await it('re-renders when the accelerator changes', async () => {
|
|
65
|
+
const host = document.createElement('adw-shortcut-label');
|
|
66
|
+
host.setAttribute('accelerator', '<Control>C');
|
|
67
|
+
document.body.appendChild(host);
|
|
68
|
+
|
|
69
|
+
let notified = 0;
|
|
70
|
+
host.addEventListener('notify::accelerator', () => notified++);
|
|
71
|
+
host.setAttribute('accelerator', '<Control>V');
|
|
72
|
+
|
|
73
|
+
expect(serialize(host)).toBe('[Ctrl][V]');
|
|
74
|
+
expect(notified).toBe(1);
|
|
75
|
+
|
|
76
|
+
host.remove();
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
await it('draws the keycaps of one combination left-to-right inside an RTL context', async () => {
|
|
80
|
+
// `gtk_widget_set_direction (box, GTK_TEXT_DIR_LTR)` (:380): the
|
|
81
|
+
// modifier order belongs to the shortcut, not to the surrounding
|
|
82
|
+
// text — only the SEQUENCE arrow flips.
|
|
83
|
+
const host = document.createElement('adw-shortcut-label');
|
|
84
|
+
host.setAttribute('dir', 'rtl');
|
|
85
|
+
host.setAttribute('accelerator', '<Control>C+<Control>X');
|
|
86
|
+
document.body.appendChild(host);
|
|
87
|
+
|
|
88
|
+
const box = host.querySelector<HTMLElement>('.adw-shortcut-label-keys');
|
|
89
|
+
expect(box?.getAttribute('dir')).toBe('ltr');
|
|
90
|
+
expect(getComputedStyle(box as HTMLElement).direction).toBe('ltr');
|
|
91
|
+
expect(host.querySelector('.dimmed')?.textContent).toBe('←');
|
|
92
|
+
|
|
93
|
+
host.remove();
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
await it('gives its keycaps the styling libadwaita gives them', async () => {
|
|
97
|
+
// The rule exists, and it reaches the node the element builds — the
|
|
98
|
+
// half a class-name assertion cannot see (`_shortcuts-dialog.scss:42-53`).
|
|
99
|
+
const host = document.createElement('adw-shortcut-label');
|
|
100
|
+
host.setAttribute('accelerator', '<Control>C');
|
|
101
|
+
document.body.appendChild(host);
|
|
102
|
+
|
|
103
|
+
const cap = host.querySelector<HTMLElement>('.keycap');
|
|
104
|
+
const style = getComputedStyle(cap as HTMLElement);
|
|
105
|
+
expect(style.borderTopLeftRadius).toBe('6px');
|
|
106
|
+
expect(style.paddingTop).toBe('6px');
|
|
107
|
+
expect(Number.parseFloat(style.minWidth)).toBe(20);
|
|
108
|
+
|
|
109
|
+
host.remove();
|
|
110
|
+
});
|
|
111
|
+
});
|
|
112
|
+
};
|