@gjsify/adwaita-web 0.30.0 → 0.32.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/adwaita-web.css +786 -272
- package/dist/adwaita-web.css.map +1 -1
- package/lib/types/adw-about-dialog.spec.d.ts +1 -0
- package/lib/types/adw-action-rows.spec.d.ts +1 -0
- package/lib/types/adw-avatar.spec.d.ts +1 -0
- package/lib/types/adw-banner.spec.d.ts +1 -0
- package/lib/types/adw-button-content.spec.d.ts +1 -0
- package/lib/types/adw-carousel.spec.d.ts +1 -0
- package/lib/types/adw-checks.spec.d.ts +1 -0
- package/lib/types/adw-header-bar.spec.d.ts +1 -0
- package/lib/types/adw-icon.spec.d.ts +1 -0
- package/lib/types/adw-progress-bar.spec.d.ts +1 -0
- package/lib/types/adw-sidebar.spec.d.ts +1 -0
- package/lib/types/adw-switch.spec.d.ts +1 -0
- package/lib/types/adw-wrap-box.spec.d.ts +1 -0
- package/lib/types/bottom-sheet.spec.d.ts +1 -0
- package/lib/types/breakpoints.d.ts +22 -0
- package/lib/types/breakpoints.spec.d.ts +1 -0
- package/lib/types/chrome.spec.d.ts +1 -0
- package/lib/types/elements/adw-about-dialog.d.ts +47 -8
- package/lib/types/elements/adw-action-row.d.ts +35 -0
- package/lib/types/elements/adw-avatar.d.ts +11 -0
- package/lib/types/elements/adw-bottom-sheet.d.ts +33 -4
- package/lib/types/elements/adw-button-content.d.ts +12 -0
- package/lib/types/elements/adw-button-row.d.ts +10 -1
- package/lib/types/elements/adw-carousel.d.ts +111 -8
- package/lib/types/elements/adw-checks.d.ts +57 -0
- package/lib/types/elements/adw-clamp.d.ts +4 -1
- package/lib/types/elements/adw-data-grid.d.ts +11 -31
- package/lib/types/elements/adw-drop-down.d.ts +17 -20
- package/lib/types/elements/adw-entry-row.d.ts +93 -3
- package/lib/types/elements/adw-expander-row.d.ts +7 -1
- package/lib/types/elements/adw-header-bar.d.ts +15 -0
- package/lib/types/elements/adw-icon.d.ts +25 -0
- package/lib/types/elements/adw-inline-view-switcher.d.ts +37 -11
- package/lib/types/elements/adw-menu-button.d.ts +6 -6
- package/lib/types/elements/adw-navigation-split-view.d.ts +6 -0
- package/lib/types/elements/adw-navigation-view.d.ts +63 -20
- package/lib/types/elements/adw-overlay-split-view.d.ts +38 -0
- package/lib/types/elements/adw-password-entry-row.d.ts +19 -10
- package/lib/types/elements/adw-popover.d.ts +58 -0
- package/lib/types/elements/adw-preferences-dialog.d.ts +57 -2
- package/lib/types/elements/adw-preferences-group.d.ts +31 -0
- package/lib/types/elements/adw-progress-bar.d.ts +38 -0
- package/lib/types/elements/adw-sidebar.d.ts +37 -5
- package/lib/types/elements/adw-split-button.d.ts +53 -7
- package/lib/types/elements/adw-switch-row.d.ts +11 -2
- package/lib/types/elements/adw-switch.d.ts +15 -0
- package/lib/types/elements/adw-tab-view.d.ts +129 -12
- package/lib/types/elements/adw-toolbar-view.d.ts +5 -0
- package/lib/types/elements/adw-view-stack.d.ts +27 -19
- package/lib/types/elements/adw-view-switcher-bar.d.ts +23 -7
- package/lib/types/elements/adw-view-switcher.d.ts +22 -11
- package/lib/types/elements/adw-window-title.d.ts +3 -1
- package/lib/types/elements/view-switcher-dom.d.ts +44 -0
- package/lib/types/entry-rows.spec.d.ts +1 -0
- package/lib/types/index.d.ts +7 -0
- package/lib/types/navigation-view.spec.d.ts +1 -0
- package/lib/types/preferences-search.d.ts +20 -0
- package/lib/types/preferences.spec.d.ts +1 -0
- package/lib/types/split-button.spec.d.ts +1 -0
- package/lib/types/split-views.spec.d.ts +1 -0
- package/lib/types/styles.generated.d.ts +1 -1
- package/lib/types/tab-view.spec.d.ts +1 -0
- package/lib/types/view-stack.spec.d.ts +1 -0
- package/lib/types/view-switcher.spec.d.ts +1 -0
- package/package.json +6 -6
- package/scss/_banner.scss +13 -0
- package/scss/_bottom_sheet.scss +5 -6
- package/scss/_button.scss +3 -26
- package/scss/_button_content.scss +28 -0
- package/scss/_checks.scss +228 -0
- package/scss/_clamp.scss +9 -3
- package/scss/_data_grid.scss +15 -7
- package/scss/_drop_down.scss +12 -55
- package/scss/_expander_row.scss +5 -50
- package/scss/_icon.scss +51 -0
- package/scss/_icons.generated.scss +6 -0
- package/scss/_inline_view_switcher.scss +34 -0
- package/scss/_labels.scss +135 -0
- package/scss/_menu_button.scss +10 -70
- package/scss/_popover.scss +186 -0
- package/scss/_preferences.scss +29 -0
- package/scss/_preferences_dialog.scss +17 -0
- package/scss/_progress_bar.scss +161 -0
- package/scss/_reset.scss +18 -7
- package/scss/_spinner.scss +7 -3
- package/scss/_split_button.scss +6 -50
- package/scss/_switch.scss +90 -0
- package/scss/_switch_row.scss +6 -52
- package/scss/_tab_view.scss +64 -0
- package/scss/_theme.scss +10 -0
- package/scss/_toolbar_view.scss +74 -0
- package/scss/_variables.scss +34 -0
- package/scss/_view_switcher.scss +40 -0
- package/scss/_view_switcher_bar.scss +37 -0
- package/scss/_wrap_box.scss +56 -6
- package/scss/adwaita-skin.scss +23 -0
- package/src/adw-about-dialog.spec.ts +350 -0
- package/src/adw-action-rows.spec.ts +342 -0
- package/src/adw-avatar.spec.ts +139 -0
- package/src/adw-banner.spec.ts +152 -0
- package/src/adw-button-content.spec.ts +183 -0
- package/src/adw-button-row.spec.ts +12 -6
- package/src/adw-carousel.spec.ts +389 -0
- package/src/adw-checks.spec.ts +368 -0
- package/src/adw-drop-down.spec.ts +123 -0
- package/src/adw-header-bar.spec.ts +83 -0
- package/src/adw-icon.spec.ts +167 -0
- package/src/adw-progress-bar.spec.ts +173 -0
- package/src/adw-sidebar.spec.ts +332 -0
- package/src/adw-switch.spec.ts +242 -0
- package/src/adw-tab-view.spec.ts +12 -3
- package/src/adw-view-switcher-bar.spec.ts +61 -13
- package/src/adw-wrap-box.spec.ts +194 -0
- package/src/bottom-sheet.spec.ts +325 -0
- package/src/breakpoints.spec.ts +147 -0
- package/src/breakpoints.ts +67 -0
- package/src/chrome.spec.ts +301 -0
- package/src/elements/adw-about-dialog.ts +223 -163
- package/src/elements/adw-action-row.ts +120 -10
- package/src/elements/adw-avatar.ts +48 -54
- package/src/elements/adw-banner.ts +46 -10
- package/src/elements/adw-bottom-sheet.ts +145 -60
- package/src/elements/adw-button-content.ts +107 -33
- package/src/elements/adw-button-row.ts +61 -37
- package/src/elements/adw-button.ts +5 -7
- package/src/elements/adw-carousel.ts +467 -105
- package/src/elements/adw-checks.ts +299 -0
- package/src/elements/adw-clamp.ts +80 -10
- package/src/elements/adw-combo-row.ts +15 -7
- package/src/elements/adw-data-grid.ts +43 -91
- package/src/elements/adw-drop-down.ts +117 -128
- package/src/elements/adw-entry-row.ts +404 -36
- package/src/elements/adw-expander-row.ts +61 -29
- package/src/elements/adw-header-bar.ts +31 -6
- package/src/elements/adw-icon.ts +148 -0
- package/src/elements/adw-inline-view-switcher.ts +240 -129
- package/src/elements/adw-menu-button.ts +93 -70
- package/src/elements/adw-navigation-split-view.ts +48 -7
- package/src/elements/adw-navigation-view.ts +286 -148
- package/src/elements/adw-overlay-split-view.ts +164 -14
- package/src/elements/adw-password-entry-row.ts +111 -89
- package/src/elements/adw-popover.ts +283 -0
- package/src/elements/adw-preferences-dialog.ts +120 -21
- package/src/elements/adw-preferences-group.ts +128 -22
- package/src/elements/adw-progress-bar.ts +189 -0
- package/src/elements/adw-sidebar.ts +301 -142
- package/src/elements/adw-spin-row.ts +11 -5
- package/src/elements/adw-spinner.ts +23 -8
- package/src/elements/adw-split-button.ts +371 -92
- package/src/elements/adw-status-page.ts +9 -9
- package/src/elements/adw-switch-row.ts +100 -30
- package/src/elements/adw-switch.ts +129 -0
- package/src/elements/adw-tab-view.ts +682 -129
- package/src/elements/adw-toggle-group.ts +5 -7
- package/src/elements/adw-toolbar-view.ts +84 -21
- package/src/elements/adw-view-stack.ts +162 -85
- package/src/elements/adw-view-switcher-bar.ts +163 -81
- package/src/elements/adw-view-switcher.ts +231 -112
- package/src/elements/adw-window-title.ts +44 -11
- package/src/elements/adw-wrap-box.ts +131 -35
- package/src/elements/view-switcher-dom.ts +108 -0
- package/src/entry-rows.spec.ts +391 -0
- package/src/index.ts +23 -0
- package/src/navigation-view.spec.ts +435 -0
- package/src/preferences-search.ts +135 -0
- package/src/preferences.spec.ts +250 -0
- package/src/source-view/adw-source-view.ts +2 -5
- package/src/split-button.spec.ts +470 -0
- package/src/split-views.spec.ts +309 -0
- package/src/styles.generated.ts +1 -1
- package/src/tab-view.spec.ts +451 -0
- package/src/test.browser.mts +49 -0
- package/src/view-stack.spec.ts +272 -0
- package/src/view-switcher.spec.ts +446 -0
|
@@ -0,0 +1,342 @@
|
|
|
1
|
+
// DOM-level conformance tests for the action-row family — <adw-action-row>,
|
|
2
|
+
// <adw-switch-row>, <adw-button-row> and <adw-window-title> — driven by the SAME
|
|
3
|
+
// vectors the NativeScript renderer asserts against
|
|
4
|
+
// (`@gjsify/adwaita-core/conformance`).
|
|
5
|
+
//
|
|
6
|
+
// What this suite exists to catch, in the words of what it found:
|
|
7
|
+
// - NEITHER renderer hid an empty TITLE. `string_is_not_empty` is bound to the
|
|
8
|
+
// title label as much as to the subtitle (adw-action-row.ui:49-53 vs :71-75),
|
|
9
|
+
// and the browser port hand-rolled the subtitle-only half of that block six
|
|
10
|
+
// times over.
|
|
11
|
+
// - a programmatic `switchRow.active = true` emitted nothing here, because the
|
|
12
|
+
// DOM does not fire `change` for a scripted `.checked =` — while the
|
|
13
|
+
// NativeScript port, whose `checkedChange` does fire, emitted. One C source,
|
|
14
|
+
// two opposite answers, and nothing comparing them.
|
|
15
|
+
// - clicking anywhere but the handle of a switch row did nothing, though
|
|
16
|
+
// `adw_switch_row_init` makes the whole row the switch's activator.
|
|
17
|
+
// - `<adw-button-row activatable="false">` was honoured, an opt-out libadwaita
|
|
18
|
+
// does not have — and `<adw-action-row activatable="false">` meant the
|
|
19
|
+
// OPPOSITE, because that element reads the attribute by presence.
|
|
20
|
+
import { describe, expect, it } from '@gjsify/unit';
|
|
21
|
+
|
|
22
|
+
import {
|
|
23
|
+
ACTION_ROW_ACTIVATION_VECTORS,
|
|
24
|
+
BUTTON_ROW_ACTIVATABLE_VECTORS,
|
|
25
|
+
BUTTON_ROW_ICON_VECTORS,
|
|
26
|
+
LABEL_VISIBILITY_VECTORS,
|
|
27
|
+
SWITCH_ROW_NOTIFY_VECTORS,
|
|
28
|
+
WINDOW_TITLE_VECTORS,
|
|
29
|
+
} from '@gjsify/adwaita-core/conformance';
|
|
30
|
+
|
|
31
|
+
import { normalizeIconName } from '@gjsify/adwaita-core';
|
|
32
|
+
|
|
33
|
+
import type { AdwActionRow } from './elements/adw-action-row.js';
|
|
34
|
+
import type { AdwSwitchRow } from './elements/adw-switch-row.js';
|
|
35
|
+
import type { AdwWindowTitle } from './elements/adw-window-title.js';
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Mount an element imperatively — never through parsed HTML, because several
|
|
39
|
+
* label vectors hinge on exact whitespace (`' '`, `'\t'`) that an attribute
|
|
40
|
+
* value in markup is not a reliable carrier for.
|
|
41
|
+
*/
|
|
42
|
+
function mount<T extends HTMLElement>(tag: string): { el: T; host: HTMLElement } {
|
|
43
|
+
const host = document.createElement('div');
|
|
44
|
+
document.body.appendChild(host);
|
|
45
|
+
const el = document.createElement(tag) as T;
|
|
46
|
+
host.appendChild(el);
|
|
47
|
+
return { el, host };
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/** Set an attribute, where `null` means "the property is unset". */
|
|
51
|
+
function setAttr(el: HTMLElement, name: string, value: string | null) {
|
|
52
|
+
if (value === null) el.removeAttribute(name);
|
|
53
|
+
else el.setAttribute(name, value);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/** Whether the label matching `selector` is rendered at all. */
|
|
57
|
+
function labelVisible(el: HTMLElement, selector: string): boolean {
|
|
58
|
+
const label = el.querySelector(selector) as HTMLElement | null;
|
|
59
|
+
return label !== null && !label.hidden;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/** Collect the `detail` of every `event` dispatched on `el`. */
|
|
63
|
+
function record(el: HTMLElement, event: string): unknown[] {
|
|
64
|
+
const details: unknown[] = [];
|
|
65
|
+
el.addEventListener(event, (e) => details.push((e as CustomEvent).detail));
|
|
66
|
+
return details;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/** Let a MutationObserver callback (the sensitivity binding) be delivered. */
|
|
70
|
+
const tick = () => new Promise((resolve) => setTimeout(resolve, 0));
|
|
71
|
+
|
|
72
|
+
export const AdwActionRowsTest = async () => {
|
|
73
|
+
await describe('<adw-action-row> label visibility (libadwaita conformance vectors)', async () => {
|
|
74
|
+
for (const { text, visible, rule } of LABEL_VISIBILITY_VECTORS) {
|
|
75
|
+
await it(`title ${JSON.stringify(text)} → ${visible} — ${rule}`, () => {
|
|
76
|
+
const { el, host } = mount<AdwActionRow>('adw-action-row');
|
|
77
|
+
setAttr(el, 'title', text);
|
|
78
|
+
expect(labelVisible(el, '.adw-row-title')).toBe(visible);
|
|
79
|
+
host.remove();
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
await it(`subtitle ${JSON.stringify(text)} → ${visible} — ${rule}`, () => {
|
|
83
|
+
const { el, host } = mount<AdwActionRow>('adw-action-row');
|
|
84
|
+
setAttr(el, 'subtitle', text);
|
|
85
|
+
expect(labelVisible(el, '.adw-row-subtitle')).toBe(visible);
|
|
86
|
+
host.remove();
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
await it('renders the text it was given, whitespace included', () => {
|
|
91
|
+
const { el, host } = mount<AdwActionRow>('adw-action-row');
|
|
92
|
+
el.setAttribute('title', ' ');
|
|
93
|
+
expect((el.querySelector('.adw-row-title') as HTMLElement).textContent).toBe(' ');
|
|
94
|
+
host.remove();
|
|
95
|
+
});
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
await describe('<adw-action-row> activatable-widget (libadwaita conformance vectors)', async () => {
|
|
99
|
+
for (const vector of ACTION_ROW_ACTIVATION_VECTORS) {
|
|
100
|
+
await it(`${vector.name} — ${vector.rule}`, async () => {
|
|
101
|
+
const { el: row, host } = mount<AdwActionRow>('adw-action-row');
|
|
102
|
+
const widgets = new Map<string, HTMLButtonElement>();
|
|
103
|
+
let last: HTMLButtonElement | null = null;
|
|
104
|
+
|
|
105
|
+
for (const step of vector.steps) {
|
|
106
|
+
switch (step.op) {
|
|
107
|
+
case 'set-activatable-widget': {
|
|
108
|
+
if (step.widget === null) {
|
|
109
|
+
row.activatableWidget = null;
|
|
110
|
+
break;
|
|
111
|
+
}
|
|
112
|
+
let widget = widgets.get(step.widget);
|
|
113
|
+
if (!widget) {
|
|
114
|
+
widget = document.createElement('button');
|
|
115
|
+
widget.slot = 'suffix';
|
|
116
|
+
row.suffixSection.appendChild(widget);
|
|
117
|
+
widgets.set(step.widget, widget);
|
|
118
|
+
}
|
|
119
|
+
// `GtkWidget:sensitive` — `disabled` is its DOM name.
|
|
120
|
+
widget.disabled = step.sensitive === false;
|
|
121
|
+
row.activatableWidget = widget;
|
|
122
|
+
last = widget;
|
|
123
|
+
break;
|
|
124
|
+
}
|
|
125
|
+
case 'set-widget-sensitive':
|
|
126
|
+
if (last) last.disabled = !step.sensitive;
|
|
127
|
+
// The binding runs through a MutationObserver.
|
|
128
|
+
await tick();
|
|
129
|
+
break;
|
|
130
|
+
case 'set-activatable':
|
|
131
|
+
row.toggleAttribute('activatable', step.activatable);
|
|
132
|
+
break;
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
expect(row.activatable).toBe(vector.activatable);
|
|
137
|
+
expect(row.classList.contains('activatable')).toBe(vector.activatable);
|
|
138
|
+
const expectedWidget = vector.activatableWidget === null ? null : widgets.get(vector.activatableWidget);
|
|
139
|
+
expect(row.activatableWidget).toBe(expectedWidget ?? null);
|
|
140
|
+
host.remove();
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
await it('reads `activatable` by PRESENCE, like every other boolean attribute here', () => {
|
|
145
|
+
const { el: row, host } = mount<AdwActionRow>('adw-action-row');
|
|
146
|
+
row.setAttribute('activatable', 'false');
|
|
147
|
+
// The HTML boolean-attribute convention (`<input disabled="false">`
|
|
148
|
+
// is disabled), which is how every other boolean attribute in this
|
|
149
|
+
// package is read. NOT a libadwaita rule — libadwaita has a typed
|
|
150
|
+
// gboolean and no attribute parsing at all — so it is pinned here
|
|
151
|
+
// rather than in the cross-renderer vectors. Pinned so the next
|
|
152
|
+
// reader does not "fix" it into the string comparison
|
|
153
|
+
// <adw-button-row> used to carry, which gave one markup two
|
|
154
|
+
// opposite meanings inside one package.
|
|
155
|
+
expect(row.activatable).toBe(true);
|
|
156
|
+
host.remove();
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
await it('forwards an activation to the activatable widget, then emits `activated`', () => {
|
|
160
|
+
const { el: row, host } = mount<AdwActionRow>('adw-action-row');
|
|
161
|
+
const widget = document.createElement('button');
|
|
162
|
+
row.suffixSection.appendChild(widget);
|
|
163
|
+
let widgetClicks = 0;
|
|
164
|
+
widget.addEventListener('click', () => widgetClicks++);
|
|
165
|
+
row.activatableWidget = widget;
|
|
166
|
+
|
|
167
|
+
const activations = record(row, 'activated');
|
|
168
|
+
row.click();
|
|
169
|
+
|
|
170
|
+
expect(widgetClicks).toBe(1);
|
|
171
|
+
expect(activations.length).toBe(1);
|
|
172
|
+
host.remove();
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
await it('leaves a click that came from the widget itself to the widget', () => {
|
|
176
|
+
const { el: row, host } = mount<AdwActionRow>('adw-action-row');
|
|
177
|
+
const widget = document.createElement('button');
|
|
178
|
+
row.suffixSection.appendChild(widget);
|
|
179
|
+
let widgetClicks = 0;
|
|
180
|
+
widget.addEventListener('click', () => widgetClicks++);
|
|
181
|
+
row.activatableWidget = widget;
|
|
182
|
+
const activations = record(row, 'activated');
|
|
183
|
+
|
|
184
|
+
widget.click();
|
|
185
|
+
|
|
186
|
+
// One click, and no row activation: GtkListBox does not emit
|
|
187
|
+
// `row-activated` for a click a child widget claimed. Re-clicking the
|
|
188
|
+
// widget here would also bounce back into the row's own listener.
|
|
189
|
+
expect(widgetClicks).toBe(1);
|
|
190
|
+
expect(activations.length).toBe(0);
|
|
191
|
+
host.remove();
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
await it('stays silent while unactivatable', () => {
|
|
195
|
+
const { el: row, host } = mount<AdwActionRow>('adw-action-row');
|
|
196
|
+
const activations = record(row, 'activated');
|
|
197
|
+
row.click();
|
|
198
|
+
expect(activations.length).toBe(0);
|
|
199
|
+
host.remove();
|
|
200
|
+
});
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
await describe('<adw-switch-row> notify::active (libadwaita conformance vectors)', async () => {
|
|
204
|
+
for (const vector of SWITCH_ROW_NOTIFY_VECTORS) {
|
|
205
|
+
await it(`${vector.name} — ${vector.rule}`, () => {
|
|
206
|
+
const { el: row, host } = mount<AdwSwitchRow>('adw-switch-row');
|
|
207
|
+
const events = record(row, 'notify::active');
|
|
208
|
+
|
|
209
|
+
for (const step of vector.steps) {
|
|
210
|
+
if (step.op === 'set-active') row.active = step.active;
|
|
211
|
+
else row.click();
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
expect(row.active).toBe(vector.active);
|
|
215
|
+
expect((row.querySelector('input[type="checkbox"]') as HTMLInputElement).checked).toBe(vector.active);
|
|
216
|
+
expect(events.map((detail) => (detail as { active: boolean }).active)).toStrictEqual([
|
|
217
|
+
...vector.emitted,
|
|
218
|
+
]);
|
|
219
|
+
host.remove();
|
|
220
|
+
});
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
await it('toggles from a click on the handle, without double-toggling', () => {
|
|
224
|
+
const { el: row, host } = mount<AdwSwitchRow>('adw-switch-row');
|
|
225
|
+
const events = record(row, 'notify::active');
|
|
226
|
+
const checkbox = row.querySelector('input[type="checkbox"]') as HTMLInputElement;
|
|
227
|
+
|
|
228
|
+
checkbox.click();
|
|
229
|
+
|
|
230
|
+
expect(row.active).toBe(true);
|
|
231
|
+
expect(events.length).toBe(1);
|
|
232
|
+
host.remove();
|
|
233
|
+
});
|
|
234
|
+
|
|
235
|
+
await it('keeps aria-checked in step with the state (adw-switch-row.c:71-74)', () => {
|
|
236
|
+
const { el: row, host } = mount<AdwSwitchRow>('adw-switch-row');
|
|
237
|
+
expect(row.getAttribute('aria-checked')).toBe('false');
|
|
238
|
+
row.active = true;
|
|
239
|
+
expect(row.getAttribute('aria-checked')).toBe('true');
|
|
240
|
+
host.remove();
|
|
241
|
+
});
|
|
242
|
+
|
|
243
|
+
for (const { text, visible, rule } of LABEL_VISIBILITY_VECTORS) {
|
|
244
|
+
await it(`title ${JSON.stringify(text)} → ${visible} — ${rule}`, () => {
|
|
245
|
+
const { el, host } = mount<AdwSwitchRow>('adw-switch-row');
|
|
246
|
+
setAttr(el, 'title', text);
|
|
247
|
+
expect(labelVisible(el, '.adw-row-title')).toBe(visible);
|
|
248
|
+
host.remove();
|
|
249
|
+
});
|
|
250
|
+
}
|
|
251
|
+
});
|
|
252
|
+
|
|
253
|
+
await describe('<adw-button-row> (libadwaita conformance vectors)', async () => {
|
|
254
|
+
for (const vector of BUTTON_ROW_ICON_VECTORS) {
|
|
255
|
+
await it(`${JSON.stringify([vector.startIconName, vector.endIconName])} — ${vector.rule}`, () => {
|
|
256
|
+
const { el: row, host } = mount<HTMLElement>('adw-button-row');
|
|
257
|
+
setAttr(row, 'start-icon-name', vector.startIconName);
|
|
258
|
+
setAttr(row, 'end-icon-name', vector.endIconName);
|
|
259
|
+
|
|
260
|
+
// The two `image.icon.{start,end}` nodes are <adw-icon> now, not
|
|
261
|
+
// hand-rolled decorative spans.
|
|
262
|
+
const icons = Array.from(row.querySelectorAll('adw-icon')) as HTMLElement[];
|
|
263
|
+
expect(icons.length).toBe(2);
|
|
264
|
+
expect(!icons[0]!.hidden).toBe(vector.startIconVisible);
|
|
265
|
+
expect(!icons[1]!.hidden).toBe(vector.endIconVisible);
|
|
266
|
+
// The mask class is the NORMALIZED name: the generated classes
|
|
267
|
+
// never carry `-symbolic`, and this row used to interpolate the
|
|
268
|
+
// raw one — so `start-icon-name="list-add-symbolic"` asked for a
|
|
269
|
+
// class that has never existed and drew an empty box.
|
|
270
|
+
if (vector.startIconVisible) {
|
|
271
|
+
expect(icons[0]!.classList.contains(`adw-icon--${normalizeIconName(vector.startIconName)}`)).toBe(
|
|
272
|
+
true,
|
|
273
|
+
);
|
|
274
|
+
}
|
|
275
|
+
if (vector.endIconVisible) {
|
|
276
|
+
expect(icons[1]!.classList.contains(`adw-icon--${normalizeIconName(vector.endIconName)}`)).toBe(
|
|
277
|
+
true,
|
|
278
|
+
);
|
|
279
|
+
}
|
|
280
|
+
host.remove();
|
|
281
|
+
});
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
for (const vector of BUTTON_ROW_ACTIVATABLE_VECTORS) {
|
|
285
|
+
await it(`activatable=${JSON.stringify(vector.declared)} — ${vector.rule}`, () => {
|
|
286
|
+
const { el: row, host } = mount<HTMLElement>('adw-button-row');
|
|
287
|
+
setAttr(row, 'activatable', vector.declared);
|
|
288
|
+
const activations = record(row, 'activated');
|
|
289
|
+
row.click();
|
|
290
|
+
|
|
291
|
+
expect(row.classList.contains('activatable')).toBe(vector.activatable);
|
|
292
|
+
expect(activations.length).toBe(1);
|
|
293
|
+
host.remove();
|
|
294
|
+
});
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
for (const { text, visible, rule } of LABEL_VISIBILITY_VECTORS) {
|
|
298
|
+
await it(`title ${JSON.stringify(text)} → ${visible} — ${rule}`, () => {
|
|
299
|
+
const { el: row, host } = mount<HTMLElement>('adw-button-row');
|
|
300
|
+
setAttr(row, 'title', text);
|
|
301
|
+
expect(labelVisible(row, '.adw-button-row-title')).toBe(visible);
|
|
302
|
+
host.remove();
|
|
303
|
+
});
|
|
304
|
+
}
|
|
305
|
+
});
|
|
306
|
+
|
|
307
|
+
await describe('<adw-window-title> (libadwaita conformance vectors)', async () => {
|
|
308
|
+
for (const vector of WINDOW_TITLE_VECTORS) {
|
|
309
|
+
await it(`${vector.name} — ${vector.rule}`, () => {
|
|
310
|
+
const { el, host } = mount<AdwWindowTitle>('adw-window-title');
|
|
311
|
+
const notified: string[] = [];
|
|
312
|
+
el.addEventListener('notify::title', () => notified.push('title'));
|
|
313
|
+
el.addEventListener('notify::subtitle', () => notified.push('subtitle'));
|
|
314
|
+
|
|
315
|
+
for (const step of vector.steps) {
|
|
316
|
+
setAttr(el, step.op === 'set-title' ? 'title' : 'subtitle', step.value);
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
const title = el.querySelector('.adw-window-title-title') as HTMLElement;
|
|
320
|
+
const subtitle = el.querySelector('.adw-window-title-subtitle') as HTMLElement;
|
|
321
|
+
expect(title.textContent).toBe(vector.title);
|
|
322
|
+
expect(!title.hidden).toBe(vector.titleVisible);
|
|
323
|
+
expect(subtitle.textContent).toBe(vector.subtitle);
|
|
324
|
+
expect(!subtitle.hidden).toBe(vector.subtitleVisible);
|
|
325
|
+
expect(notified).toStrictEqual([...vector.notified]);
|
|
326
|
+
host.remove();
|
|
327
|
+
});
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
await it('adopts a declared title without calling it a change', () => {
|
|
331
|
+
const host = document.createElement('div');
|
|
332
|
+
document.body.appendChild(host);
|
|
333
|
+
host.innerHTML = '<adw-window-title title="Documents"></adw-window-title>';
|
|
334
|
+
const el = host.querySelector('adw-window-title') as AdwWindowTitle;
|
|
335
|
+
const title = el.querySelector('.adw-window-title-title') as HTMLElement;
|
|
336
|
+
|
|
337
|
+
expect(title.textContent).toBe('Documents');
|
|
338
|
+
expect(title.hidden).toBe(false);
|
|
339
|
+
host.remove();
|
|
340
|
+
});
|
|
341
|
+
});
|
|
342
|
+
};
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
// DOM-level conformance tests for <adw-avatar>, driven by the SAME vectors the
|
|
2
|
+
// NativeScript renderer asserts against (`@gjsify/adwaita-core/conformance`).
|
|
3
|
+
//
|
|
4
|
+
// The two renderers used to carry independent copies of the initials + colour
|
|
5
|
+
// derivation, and the copies had drifted apart AND away from libadwaita:
|
|
6
|
+
// this element rendered "AD" for `text="Ada"` where GTK and NS render "A", and
|
|
7
|
+
// both hashed UTF-16 code units where GLib hashes UTF-8 bytes, so every accented
|
|
8
|
+
// name was painted the wrong colour. Nothing failed, because nothing compared
|
|
9
|
+
// them. This suite is that comparison.
|
|
10
|
+
import { describe, expect, it } from '@gjsify/unit';
|
|
11
|
+
|
|
12
|
+
import { AVATAR_COLORS, avatarMaxFontSize } from '@gjsify/adwaita-core';
|
|
13
|
+
import {
|
|
14
|
+
AVATAR_COLOR_VECTORS,
|
|
15
|
+
AVATAR_FONT_SIZE_VECTORS,
|
|
16
|
+
AVATAR_INITIALS_VECTORS,
|
|
17
|
+
} from '@gjsify/adwaita-core/conformance';
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Mount an avatar and set `text` through `setAttribute`, never through parsed
|
|
21
|
+
* HTML — several vectors hinge on exact whitespace (a tab, a run of spaces),
|
|
22
|
+
* which an attribute value in markup is not a reliable carrier for.
|
|
23
|
+
*/
|
|
24
|
+
function mountAvatar(text: string, showInitials = true): { avatar: HTMLElement; host: HTMLElement } {
|
|
25
|
+
const host = document.createElement('div');
|
|
26
|
+
document.body.appendChild(host);
|
|
27
|
+
const avatar = document.createElement('adw-avatar');
|
|
28
|
+
if (showInitials) avatar.setAttribute('show-initials', '');
|
|
29
|
+
host.appendChild(avatar);
|
|
30
|
+
avatar.setAttribute('text', text);
|
|
31
|
+
return { avatar, host };
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/** The rendered initials, i.e. what a user actually sees in the circle. */
|
|
35
|
+
function renderedInitials(avatar: HTMLElement): string {
|
|
36
|
+
const text = avatar.querySelector('.adw-avatar-text') as HTMLElement | null;
|
|
37
|
+
if (!text || text.hidden) return '';
|
|
38
|
+
return text.textContent ?? '';
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/** `#rrggbb` in the `rgb(r, g, b)` form the CSSOM serialises inline styles to. */
|
|
42
|
+
function asRgb(hex: string): string {
|
|
43
|
+
const n = Number.parseInt(hex.slice(1), 16);
|
|
44
|
+
return `rgb(${(n >> 16) & 0xff}, ${(n >> 8) & 0xff}, ${n & 0xff})`;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export const AdwAvatarTest = async () => {
|
|
48
|
+
await describe('adw-avatar initials (libadwaita conformance vectors)', async () => {
|
|
49
|
+
for (const { text, initials, rule } of AVATAR_INITIALS_VECTORS) {
|
|
50
|
+
await it(`${JSON.stringify(text)} renders ${JSON.stringify(initials)} — ${rule}`, () => {
|
|
51
|
+
const { avatar, host } = mountAvatar(text);
|
|
52
|
+
expect(renderedInitials(avatar)).toBe(initials);
|
|
53
|
+
host.remove();
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
await it('falls back to the icon when there are no initials', () => {
|
|
58
|
+
const { avatar, host } = mountAvatar('');
|
|
59
|
+
expect(renderedInitials(avatar)).toBe('');
|
|
60
|
+
const icon = avatar.querySelector('.adw-avatar-icon') as HTMLElement;
|
|
61
|
+
expect(icon.hidden).toBe(false);
|
|
62
|
+
host.remove();
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
await it('shows the icon when show-initials is absent, even with a name', () => {
|
|
66
|
+
const { avatar, host } = mountAvatar('Ada Lovelace', false);
|
|
67
|
+
expect(renderedInitials(avatar)).toBe('');
|
|
68
|
+
host.remove();
|
|
69
|
+
});
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
await describe('adw-avatar colour (libadwaita conformance vectors)', async () => {
|
|
73
|
+
for (const { text, colorClass } of AVATAR_COLOR_VECTORS) {
|
|
74
|
+
// Only a genuinely EMPTY name drops to icon mode; a whitespace-only
|
|
75
|
+
// one stays in initials mode (blank label) and is still painted, so
|
|
76
|
+
// it belongs in this loop.
|
|
77
|
+
if (text.length === 0) continue;
|
|
78
|
+
|
|
79
|
+
await it(`${JSON.stringify(text)} paints color${colorClass}`, () => {
|
|
80
|
+
const { avatar, host } = mountAvatar(text);
|
|
81
|
+
const palette = AVATAR_COLORS[colorClass - 1]!;
|
|
82
|
+
expect(avatar.style.backgroundImage).toBe(
|
|
83
|
+
`linear-gradient(${asRgb(palette.start)}, ${asRgb(palette.stop)})`,
|
|
84
|
+
);
|
|
85
|
+
expect(avatar.style.color).toBe(asRgb(palette.fg));
|
|
86
|
+
host.remove();
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
await it('drops the gradient again when the avatar falls back to its icon', () => {
|
|
91
|
+
const { avatar, host } = mountAvatar('Ada Lovelace');
|
|
92
|
+
expect(avatar.style.backgroundImage).not.toBe('');
|
|
93
|
+
avatar.removeAttribute('show-initials');
|
|
94
|
+
expect(avatar.style.backgroundImage).toBe('');
|
|
95
|
+
host.remove();
|
|
96
|
+
});
|
|
97
|
+
});
|
|
98
|
+
await describe('adw-avatar font size (Adw.Avatar update_font_size)', async () => {
|
|
99
|
+
for (const { size, maxFontSize } of AVATAR_FONT_SIZE_VECTORS) {
|
|
100
|
+
await it(`size ${size} never exceeds the ${maxFontSize}px cap`, () => {
|
|
101
|
+
const { avatar, host } = mountAvatar('Ada Lovelace');
|
|
102
|
+
avatar.setAttribute('size', String(size));
|
|
103
|
+
const label = avatar.querySelector('.adw-avatar-text') as HTMLElement;
|
|
104
|
+
const applied = Number.parseFloat(label.style.fontSize);
|
|
105
|
+
// The measured aspect ratio decides the exact value; what the
|
|
106
|
+
// element must never do is overflow the circle.
|
|
107
|
+
expect(applied <= avatarMaxFontSize(size) + 0.001).toBe(true);
|
|
108
|
+
expect(applied > 0).toBe(true);
|
|
109
|
+
host.remove();
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
await it('grows monotonically across the old 31px/32px discontinuity', () => {
|
|
114
|
+
const { avatar, host } = mountAvatar('Ada Lovelace');
|
|
115
|
+
const label = avatar.querySelector('.adw-avatar-text') as HTMLElement;
|
|
116
|
+
const at = (size: number) => {
|
|
117
|
+
avatar.setAttribute('size', String(size));
|
|
118
|
+
return Number.parseFloat(label.style.fontSize);
|
|
119
|
+
};
|
|
120
|
+
// The old heuristic returned 16px at 31 and 13px at 32.
|
|
121
|
+
expect(at(32) >= at(31)).toBe(true);
|
|
122
|
+
expect(at(64) >= at(48)).toBe(true);
|
|
123
|
+
host.remove();
|
|
124
|
+
});
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
await describe('adw-avatar mode (Adw.Avatar update_visibility)', async () => {
|
|
128
|
+
await it('keeps a whitespace-only name in initials mode with a blank label', () => {
|
|
129
|
+
const { avatar, host } = mountAvatar(' ');
|
|
130
|
+
const label = avatar.querySelector('.adw-avatar-text') as HTMLElement;
|
|
131
|
+
const icon = avatar.querySelector('.adw-avatar-icon') as HTMLElement;
|
|
132
|
+
// libadwaita gates on strlen(text), not on the derived initials.
|
|
133
|
+
expect(label.hidden).toBe(false);
|
|
134
|
+
expect(label.textContent).toBe('');
|
|
135
|
+
expect(icon.hidden).toBe(true);
|
|
136
|
+
host.remove();
|
|
137
|
+
});
|
|
138
|
+
});
|
|
139
|
+
};
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
// DOM-level conformance tests for <adw-banner>, driven by the SAME vectors the
|
|
2
|
+
// NativeScript renderer asserts against (`@gjsify/adwaita-core/conformance`).
|
|
3
|
+
//
|
|
4
|
+
// The two renderers carried independent banner logic and had drifted apart AND
|
|
5
|
+
// away from libadwaita: `revealed` was opt-in here (right) and initialised TRUE
|
|
6
|
+
// on NativeScript with no `visibility` written at all (wrong), `use-markup` was
|
|
7
|
+
// opt-in in both with a comment here asserting that WAS the C default (it is
|
|
8
|
+
// TRUE, adw-banner.c:422-425), `button-style` was in neither, and the mnemonic
|
|
9
|
+
// marker the template pins onto the button (adw-banner.ui:33) was painted as a
|
|
10
|
+
// literal underscore. Nothing failed, because nothing compared them.
|
|
11
|
+
import { describe, expect, it } from '@gjsify/unit';
|
|
12
|
+
|
|
13
|
+
import { ADW_BANNER_DEFAULTS } from '@gjsify/adwaita-core';
|
|
14
|
+
import {
|
|
15
|
+
BANNER_BUTTON_STYLE_PARSE_VECTORS,
|
|
16
|
+
BANNER_BUTTON_STYLE_VECTORS,
|
|
17
|
+
BANNER_BUTTON_TEXT_VECTORS,
|
|
18
|
+
BANNER_BUTTON_VISIBLE_VECTORS,
|
|
19
|
+
BANNER_DEFAULT_VECTORS,
|
|
20
|
+
} from '@gjsify/adwaita-core/conformance';
|
|
21
|
+
import type { AdwBanner } from './elements/adw-banner.js';
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Mount a banner, setting attributes through `setAttribute` rather than parsed
|
|
25
|
+
* HTML — several vectors hinge on exact whitespace (`button-label=" "`), which an
|
|
26
|
+
* attribute value in markup is not a reliable carrier for.
|
|
27
|
+
*/
|
|
28
|
+
function mount(attributes: Record<string, string> = {}): { el: AdwBanner; host: HTMLElement } {
|
|
29
|
+
const host = document.createElement('div');
|
|
30
|
+
document.body.appendChild(host);
|
|
31
|
+
const el = document.createElement('adw-banner') as AdwBanner;
|
|
32
|
+
for (const [name, value] of Object.entries(attributes)) el.setAttribute(name, value);
|
|
33
|
+
host.appendChild(el);
|
|
34
|
+
return { el, host };
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const titleEl = (el: AdwBanner): HTMLElement => el.querySelector('.adw-banner-title') as HTMLElement;
|
|
38
|
+
const buttonEl = (el: AdwBanner): HTMLButtonElement => el.querySelector('.adw-banner-button') as HTMLButtonElement;
|
|
39
|
+
|
|
40
|
+
export const AdwBannerTest = async () => {
|
|
41
|
+
await describe('adw-banner defaults (libadwaita conformance vectors)', async () => {
|
|
42
|
+
for (const { property, value, rule } of BANNER_DEFAULT_VECTORS) {
|
|
43
|
+
if (property === 'use-markup') continue; // deliberate departure, asserted below
|
|
44
|
+
await it(`${property} → ${JSON.stringify(value)} — ${rule}`, () => {
|
|
45
|
+
const { el, host } = mount();
|
|
46
|
+
const actual: Record<string, unknown> = {
|
|
47
|
+
title: titleEl(el).textContent,
|
|
48
|
+
'button-label': buttonEl(el).textContent,
|
|
49
|
+
revealed: el.classList.contains('revealed'),
|
|
50
|
+
'button-style': buttonEl(el).classList.contains('suggested-action') ? 'suggested' : 'default',
|
|
51
|
+
};
|
|
52
|
+
expect(actual[property]).toBe(value);
|
|
53
|
+
host.remove();
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
await it('an unrevealed banner is not laid out at all', () => {
|
|
58
|
+
const { el, host } = mount({ title: 'Metered connection' });
|
|
59
|
+
expect(getComputedStyle(el).display).toBe('none');
|
|
60
|
+
el.setAttribute('revealed', '');
|
|
61
|
+
expect(getComputedStyle(el).display === 'none').toBe(false);
|
|
62
|
+
host.remove();
|
|
63
|
+
});
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
await describe('adw-banner use-markup — a DELIBERATE departure from the C default', async () => {
|
|
67
|
+
// The C default is unambiguously TRUE (adw-banner.c:422-425) and core
|
|
68
|
+
// carries it as the spec value. This renderer does NOT adopt it: Pango
|
|
69
|
+
// markup is not HTML, and defaulting a `title` attribute to `innerHTML`
|
|
70
|
+
// would make an injection sink out of a widget that is not one in GTK.
|
|
71
|
+
// The departure is pinned here so it stays a decision rather than
|
|
72
|
+
// becoming a drift — and so does its cost.
|
|
73
|
+
await it('core still reports the libadwaita default as TRUE', () => {
|
|
74
|
+
expect(ADW_BANNER_DEFAULTS.useMarkup).toBe(true);
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
await it('the element defaults to TEXT — a title with tags shows them literally', () => {
|
|
78
|
+
const { el, host } = mount({ title: '<b>Metered</b> connection', revealed: '' });
|
|
79
|
+
expect(titleEl(el).textContent).toBe('<b>Metered</b> connection');
|
|
80
|
+
expect(titleEl(el).querySelector('b')).toBe(null);
|
|
81
|
+
host.remove();
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
await it('opting in renders markup, which is the developer asserting the title is trusted', () => {
|
|
85
|
+
const { el, host } = mount({ title: '<b>Metered</b> connection', revealed: '', 'use-markup': '' });
|
|
86
|
+
expect(titleEl(el).querySelector('b')?.textContent).toBe('Metered');
|
|
87
|
+
host.remove();
|
|
88
|
+
});
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
await describe('adw-banner button visibility (libadwaita conformance vectors)', async () => {
|
|
92
|
+
for (const { label, visible, rule } of BANNER_BUTTON_VISIBLE_VECTORS) {
|
|
93
|
+
if (label === null) continue; // an absent attribute, covered below
|
|
94
|
+
await it(`${JSON.stringify(label)} → ${visible} — ${rule}`, () => {
|
|
95
|
+
const { el, host } = mount({ revealed: '', 'button-label': label });
|
|
96
|
+
expect(buttonEl(el).hidden).toBe(!visible);
|
|
97
|
+
host.remove();
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
await it('no button-label attribute is the NULL case — no button', () => {
|
|
102
|
+
const { el, host } = mount({ revealed: '' });
|
|
103
|
+
expect(buttonEl(el).hidden).toBe(true);
|
|
104
|
+
host.remove();
|
|
105
|
+
});
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
await describe('adw-banner button text (adw-banner.ui:33 pins use-underline)', async () => {
|
|
109
|
+
for (const { label, text, rule } of BANNER_BUTTON_TEXT_VECTORS) {
|
|
110
|
+
await it(`${JSON.stringify(label)} → ${JSON.stringify(text)} — ${rule}`, () => {
|
|
111
|
+
const { el, host } = mount({ revealed: '', 'button-label': label });
|
|
112
|
+
expect(buttonEl(el).textContent).toBe(text);
|
|
113
|
+
host.remove();
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
await it('the TITLE keeps its underscores — adw-banner.ui:20 pins it to use-underline=False', () => {
|
|
118
|
+
const { el, host } = mount({ revealed: '', title: 'Snap_shot paused' });
|
|
119
|
+
expect(titleEl(el).textContent).toBe('Snap_shot paused');
|
|
120
|
+
host.remove();
|
|
121
|
+
});
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
await describe('adw-banner button-style (libadwaita conformance vectors)', async () => {
|
|
125
|
+
for (const { style, classes, rule } of BANNER_BUTTON_STYLE_VECTORS) {
|
|
126
|
+
await it(`${style} → [${classes.join(', ')}] — ${rule}`, () => {
|
|
127
|
+
const { el, host } = mount({ revealed: '', 'button-label': 'Resume', 'button-style': style });
|
|
128
|
+
const button = buttonEl(el);
|
|
129
|
+
expect(button.classList.contains('suggested-action')).toBe(classes.includes('suggested-action'));
|
|
130
|
+
expect(button.classList.contains('adw-banner-button')).toBe(true);
|
|
131
|
+
host.remove();
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
for (const { input, style, rule } of BANNER_BUTTON_STYLE_PARSE_VECTORS) {
|
|
136
|
+
if (input === null) continue; // an absent attribute, covered by the defaults block
|
|
137
|
+
await it(`attribute ${JSON.stringify(input)} → ${style} — ${rule}`, () => {
|
|
138
|
+
const { el, host } = mount({ revealed: '', 'button-label': 'Resume', 'button-style': input });
|
|
139
|
+
expect(buttonEl(el).classList.contains('suggested-action')).toBe(style === 'suggested');
|
|
140
|
+
host.remove();
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
await it('switching back to default takes the class off again (:766)', () => {
|
|
145
|
+
const { el, host } = mount({ revealed: '', 'button-label': 'Resume', 'button-style': 'suggested' });
|
|
146
|
+
expect(buttonEl(el).classList.contains('suggested-action')).toBe(true);
|
|
147
|
+
el.setAttribute('button-style', 'default');
|
|
148
|
+
expect(buttonEl(el).classList.contains('suggested-action')).toBe(false);
|
|
149
|
+
host.remove();
|
|
150
|
+
});
|
|
151
|
+
});
|
|
152
|
+
};
|