@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
|
@@ -1,13 +1,10 @@
|
|
|
1
1
|
// DOM-level conformance tests for <adw-about-dialog>, driven by the vectors in
|
|
2
2
|
// `@gjsify/adwaita-core/conformance` — the same table the core suite asserts.
|
|
3
3
|
//
|
|
4
|
-
//
|
|
5
|
-
//
|
|
6
|
-
//
|
|
7
|
-
//
|
|
8
|
-
// puts them (adw-about-dialog.ui:146-186), and `website` was treated as Details
|
|
9
|
-
// content, which is exactly the one input libadwaita leaves OUT of that
|
|
10
|
-
// predicate (:1108). Nothing compared the two, so nothing failed.
|
|
4
|
+
// A local copy of `parse_person`, of the page-visibility rules or of the fallbacks
|
|
5
|
+
// diverges from libadwaita in thirteen ways, two of them structural: Support Questions
|
|
6
|
+
// and Report an Issue belong on the MAIN page, not one navigation step deeper on Details;
|
|
7
|
+
// and `website` is the one input the Details-visibility predicate leaves OUT.
|
|
11
8
|
import { describe, expect, it } from '@gjsify/unit';
|
|
12
9
|
|
|
13
10
|
import { ADW_ABOUT_DIALOG_LABELS, stripMnemonic } from '@gjsify/adwaita-core';
|
|
@@ -33,9 +30,9 @@ interface Mounted {
|
|
|
33
30
|
}
|
|
34
31
|
|
|
35
32
|
/**
|
|
36
|
-
* Mount a dialog and set every attribute imperatively
|
|
37
|
-
*
|
|
38
|
-
*
|
|
33
|
+
* Mount a dialog and set every attribute imperatively: several vectors hinge on exact
|
|
34
|
+
* whitespace, which markup is not a reliable carrier for. `window.open` is stubbed
|
|
35
|
+
* because the link rows really do call it.
|
|
39
36
|
*/
|
|
40
37
|
function mount(attributes: Record<string, string> = {}): Mounted {
|
|
41
38
|
const host = document.createElement('div');
|
|
@@ -85,10 +82,8 @@ function rowTitles(scope: Element | null): string[] {
|
|
|
85
82
|
}
|
|
86
83
|
|
|
87
84
|
/**
|
|
88
|
-
* Activate a row and report the URI it asked for.
|
|
89
|
-
*
|
|
90
|
-
* The URI never reaches the DOM — it is captured in the row's activation
|
|
91
|
-
* handler — so the only way to observe it is to drive the row and listen.
|
|
85
|
+
* Activate a row and report the URI it asked for. The URI never reaches the DOM — it is
|
|
86
|
+
* captured in the row's activation handler — so driving the row is the only way to see it.
|
|
92
87
|
*/
|
|
93
88
|
function activateRow(dialog: HTMLElement, row: Element): string | null {
|
|
94
89
|
let uri: string | null = null;
|
|
@@ -121,8 +116,8 @@ export const AdwAboutDialogTest = async () => {
|
|
|
121
116
|
}
|
|
122
117
|
|
|
123
118
|
await it('makes a row with an empty link a LINK row, not a plain one', () => {
|
|
124
|
-
// `if (link)` is a
|
|
125
|
-
//
|
|
119
|
+
// `if (link)` is a POINTER test in the C, so `"Ada <>"` still links, to the
|
|
120
|
+
// URI `mailto:` — collapsing "" to null loses the row type.
|
|
126
121
|
const mounted = mount();
|
|
127
122
|
mounted.dialog.developers = ['Ada <>'];
|
|
128
123
|
const row = page(mounted.dialog, 'credits')!.querySelector('adw-action-row')!;
|
|
@@ -197,9 +192,9 @@ export const AdwAboutDialogTest = async () => {
|
|
|
197
192
|
}
|
|
198
193
|
|
|
199
194
|
await it('keeps a website-only dialog on the main page with no Details row', () => {
|
|
200
|
-
// `show_details` is `has_comments || has_custom_links
|
|
201
|
-
//
|
|
202
|
-
//
|
|
195
|
+
// `show_details` is `has_comments || has_custom_links`; website is
|
|
196
|
+
// deliberately not in it, so a dialog whose only extra is a website links
|
|
197
|
+
// straight from the main page.
|
|
203
198
|
const mounted = mount({ 'application-name': 'Builder', website: 'https://example.org' });
|
|
204
199
|
expect(page(mounted.dialog, 'details')).toBe(null);
|
|
205
200
|
expect(hasRow(page(mounted.dialog, 'main'), label('websiteRow'))).toBe(true);
|
|
@@ -208,8 +203,8 @@ export const AdwAboutDialogTest = async () => {
|
|
|
208
203
|
});
|
|
209
204
|
|
|
210
205
|
await describe('adw-about-dialog main page (update_support vectors)', async () => {
|
|
211
|
-
// debug-info has no counterpart
|
|
212
|
-
//
|
|
206
|
+
// debug-info has no counterpart here (no Troubleshooting page), so the core
|
|
207
|
+
// suite asserts those rows.
|
|
213
208
|
for (const vector of ABOUT_DIALOG_SUPPORT_VECTORS.filter((row) => !row.debugInfo)) {
|
|
214
209
|
const { supportUrl, issueUrl, visible, rule } = vector;
|
|
215
210
|
await it(`support=${supportUrl} issue=${issueUrl} — ${rule}`, () => {
|
|
@@ -235,8 +230,7 @@ export const AdwAboutDialogTest = async () => {
|
|
|
235
230
|
}
|
|
236
231
|
|
|
237
232
|
await it('puts the support and issue rows on the MAIN page, not on Details', () => {
|
|
238
|
-
// adw-about-dialog.ui
|
|
239
|
-
// main page, and the class docs say so outright (:87-90).
|
|
233
|
+
// In `adw-about-dialog.ui`, `support_group` is a child of the MAIN page.
|
|
240
234
|
const mounted = mount({
|
|
241
235
|
'application-name': 'Builder',
|
|
242
236
|
'support-url': 'https://example.org/help',
|
|
@@ -304,9 +298,8 @@ export const AdwAboutDialogTest = async () => {
|
|
|
304
298
|
}
|
|
305
299
|
|
|
306
300
|
await it('titles the dialog "About", not "About <app name>"', () => {
|
|
307
|
-
// The main page's title is bound to AdwDialog:title, whose template
|
|
308
|
-
//
|
|
309
|
-
// in the header revealer instead (.ui:29-31).
|
|
301
|
+
// The main page's title is bound to AdwDialog:title, whose template default
|
|
302
|
+
// is the bare word; the application name lives in the header revealer.
|
|
310
303
|
const mounted = mount({ 'application-name': 'Builder' });
|
|
311
304
|
expect(page(mounted.dialog, 'main')!.getAttribute('title')).toBe('About');
|
|
312
305
|
mounted.dispose();
|
|
@@ -323,12 +316,11 @@ export const AdwAboutDialogTest = async () => {
|
|
|
323
316
|
});
|
|
324
317
|
|
|
325
318
|
await it('does NOT open when a handler cancels it', () => {
|
|
326
|
-
// `activate_link_cb` returns the accumulated handler result
|
|
327
|
-
//
|
|
328
|
-
//
|
|
329
|
-
//
|
|
330
|
-
//
|
|
331
|
-
// ran regardless.
|
|
319
|
+
// `activate_link_cb` returns the accumulated handler result (accumulator
|
|
320
|
+
// `g_signal_accumulator_true_handled`): a handler saying "handled"
|
|
321
|
+
// suppresses the default navigation, and `preventDefault()` is the web
|
|
322
|
+
// spelling of that — so the event must be cancelable, or `window.open` runs
|
|
323
|
+
// regardless.
|
|
332
324
|
const mounted = mount({ 'application-name': 'Builder', website: 'https://example.org' });
|
|
333
325
|
mounted.dialog.addEventListener('activate-link', (event) => event.preventDefault());
|
|
334
326
|
const row = page(mounted.dialog, 'main')!.querySelector(`adw-action-row[title="${label('websiteRow')}"]`)!;
|
|
@@ -348,19 +340,14 @@ export const AdwAboutDialogTest = async () => {
|
|
|
348
340
|
});
|
|
349
341
|
});
|
|
350
342
|
|
|
351
|
-
//
|
|
343
|
+
// An opened dialog has to OCCUPY SPACE, and no other test here can see that: they
|
|
344
|
+
// all read the built DOM, and all passed while the sheet measured 360×0.
|
|
352
345
|
//
|
|
353
|
-
//
|
|
354
|
-
//
|
|
355
|
-
//
|
|
356
|
-
//
|
|
357
|
-
//
|
|
358
|
-
// `adw-about-dialog.ui:7` declares: `content-width` 360 and no
|
|
359
|
-
// content-height, libadwaita taking the height from the content. The content
|
|
360
|
-
// could not supply one — the navigation pages were absolutely positioned, so
|
|
361
|
-
// they contributed nothing to the page stack, which contributed nothing to
|
|
362
|
-
// the view, which left the sheet at zero; and the pages, being `inset: 0` of
|
|
363
|
-
// a zero-height parent, were zero in turn.
|
|
346
|
+
// `adw-about-dialog.ui` declares `content-width` 360 and no content-height, so
|
|
347
|
+
// libadwaita takes the height from the content — which absolutely-positioned
|
|
348
|
+
// navigation pages cannot supply, since they contribute nothing to the page stack,
|
|
349
|
+
// which contributes nothing to the view, and the pages are then `inset: 0` of a
|
|
350
|
+
// zero-height parent.
|
|
364
351
|
await describe('adw-about-dialog — an opened dialog is actually on screen', async () => {
|
|
365
352
|
await it('gives its sheet a height derived from its content', () => {
|
|
366
353
|
const mounted = mount({ 'application-name': 'Adwaita Storybook', version: '0.11.0' });
|
|
@@ -368,11 +355,9 @@ export const AdwAboutDialogTest = async () => {
|
|
|
368
355
|
|
|
369
356
|
const sheet = mounted.dialog.querySelector('.adw-about-dialog-sheet') as HTMLElement;
|
|
370
357
|
const rect = sheet.getBoundingClientRect();
|
|
371
|
-
// HEIGHT only
|
|
372
|
-
//
|
|
373
|
-
//
|
|
374
|
-
// 342), so the assertion would report the harness rather than the
|
|
375
|
-
// widget. A test should assert the rule the bug broke.
|
|
358
|
+
// HEIGHT only: pinning the width needs `calc(100vw - 48px)` restated in JS,
|
|
359
|
+
// where `100vw` and `innerWidth` disagree by the scrollbar, so the assertion
|
|
360
|
+
// would report the harness rather than the widget.
|
|
376
361
|
expect(rect.height > 0).toBe(true);
|
|
377
362
|
// And not merely the header: the sheet has to be taller than the one
|
|
378
363
|
// child that had a size of its own all along, which is what made the
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
// Accent application against `ACCENT_COLOR_VECTORS` — the same table the core
|
|
2
|
+
// suite drives, here through the REAL computed style of a real element.
|
|
3
|
+
//
|
|
4
|
+
// This is what makes the table renderer-driven rather than a derivation asserted
|
|
5
|
+
// against itself: the browser resolves the custom properties and the assertions
|
|
6
|
+
// read them back with `getComputedStyle`, so a value that never reaches the
|
|
7
|
+
// cascade fails a test naming the accent.
|
|
8
|
+
import { describe, expect, it } from '@gjsify/unit';
|
|
9
|
+
import { ACCENT_COLOR_VECTORS } from '@gjsify/adwaita-core/conformance';
|
|
10
|
+
|
|
11
|
+
import '@gjsify/adwaita-web';
|
|
12
|
+
import {
|
|
13
|
+
ACCENT_BG_PROPERTY,
|
|
14
|
+
ACCENT_PROPERTY,
|
|
15
|
+
applyAdwaitaAccent,
|
|
16
|
+
clearAdwaitaAccent,
|
|
17
|
+
isAdwaitaDark,
|
|
18
|
+
} from './accent.js';
|
|
19
|
+
|
|
20
|
+
/** A custom property's resolved value, whitespace trimmed. */
|
|
21
|
+
const property = (element: HTMLElement, name: string): string =>
|
|
22
|
+
getComputedStyle(element).getPropertyValue(name).trim();
|
|
23
|
+
|
|
24
|
+
export const AdwAccentTest = async () => {
|
|
25
|
+
await describe('applyAdwaitaAccent (conformance vectors)', async () => {
|
|
26
|
+
for (const vector of ACCENT_COLOR_VECTORS) {
|
|
27
|
+
await it(`${vector.name} — the fill and the light standalone colour reach the cascade`, async () => {
|
|
28
|
+
const host = document.createElement('div');
|
|
29
|
+
host.className = 'theme-light';
|
|
30
|
+
document.body.append(host);
|
|
31
|
+
|
|
32
|
+
applyAdwaitaAccent(vector.name, { target: host });
|
|
33
|
+
|
|
34
|
+
expect(property(host, ACCENT_BG_PROPERTY)).toBe(vector.background);
|
|
35
|
+
expect(property(host, ACCENT_PROPERTY)).toBe(vector.standaloneLight);
|
|
36
|
+
|
|
37
|
+
host.remove();
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
await it(`${vector.name} — the DARK standalone colour differs and is the table's`, async () => {
|
|
41
|
+
// Asserted as a pair: a renderer that ignored `dark` would pass
|
|
42
|
+
// every light row above and still be wrong in half its uses.
|
|
43
|
+
const host = document.createElement('div');
|
|
44
|
+
host.className = 'theme-dark';
|
|
45
|
+
document.body.append(host);
|
|
46
|
+
|
|
47
|
+
applyAdwaitaAccent(vector.name, { target: host });
|
|
48
|
+
|
|
49
|
+
expect(property(host, ACCENT_PROPERTY)).toBe(vector.standaloneDark);
|
|
50
|
+
expect(vector.standaloneDark).not.toBe(vector.standaloneLight);
|
|
51
|
+
|
|
52
|
+
host.remove();
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
await describe('accent application', async () => {
|
|
58
|
+
await it('reads the resolved scheme from the element rather than assuming light', async () => {
|
|
59
|
+
const dark = document.createElement('div');
|
|
60
|
+
dark.className = 'theme-dark';
|
|
61
|
+
const light = document.createElement('div');
|
|
62
|
+
light.className = 'theme-light';
|
|
63
|
+
document.body.append(dark, light);
|
|
64
|
+
|
|
65
|
+
expect(isAdwaitaDark(dark)).toBe(true);
|
|
66
|
+
expect(isAdwaitaDark(light)).toBe(false);
|
|
67
|
+
|
|
68
|
+
// A nested element inherits the ancestor's scheme — `closest`, not a
|
|
69
|
+
// class check on the element itself, which is how the stylesheet's
|
|
70
|
+
// `.theme-dark` scope actually behaves.
|
|
71
|
+
const nested = document.createElement('span');
|
|
72
|
+
dark.append(nested);
|
|
73
|
+
expect(isAdwaitaDark(nested)).toBe(true);
|
|
74
|
+
|
|
75
|
+
dark.remove();
|
|
76
|
+
light.remove();
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
await it('an explicit dark option overrides what the element resolves to', async () => {
|
|
80
|
+
const host = document.createElement('div');
|
|
81
|
+
host.className = 'theme-light';
|
|
82
|
+
document.body.append(host);
|
|
83
|
+
|
|
84
|
+
applyAdwaitaAccent('blue', { target: host, dark: true });
|
|
85
|
+
expect(property(host, ACCENT_PROPERTY)).toBe(ACCENT_COLOR_VECTORS[0].standaloneDark);
|
|
86
|
+
|
|
87
|
+
host.remove();
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
await it('clearing restores the stylesheet value instead of leaving the override', async () => {
|
|
91
|
+
const host = document.createElement('div');
|
|
92
|
+
host.className = 'theme-light';
|
|
93
|
+
document.body.append(host);
|
|
94
|
+
|
|
95
|
+
applyAdwaitaAccent('red', { target: host });
|
|
96
|
+
expect(property(host, ACCENT_BG_PROPERTY)).toBe(ACCENT_COLOR_VECTORS[5].background);
|
|
97
|
+
|
|
98
|
+
clearAdwaitaAccent(host);
|
|
99
|
+
// Back to whatever the cascade says — which is NOT red.
|
|
100
|
+
expect(property(host, ACCENT_BG_PROPERTY)).not.toBe(ACCENT_COLOR_VECTORS[5].background);
|
|
101
|
+
|
|
102
|
+
host.remove();
|
|
103
|
+
});
|
|
104
|
+
});
|
|
105
|
+
};
|
|
@@ -3,20 +3,16 @@
|
|
|
3
3
|
// vectors the NativeScript renderer asserts against
|
|
4
4
|
// (`@gjsify/adwaita-core/conformance`).
|
|
5
5
|
//
|
|
6
|
-
//
|
|
7
|
-
// -
|
|
8
|
-
//
|
|
9
|
-
//
|
|
10
|
-
//
|
|
11
|
-
//
|
|
12
|
-
//
|
|
13
|
-
//
|
|
14
|
-
//
|
|
15
|
-
//
|
|
16
|
-
// `adw_switch_row_init` makes the whole row the switch's activator.
|
|
17
|
-
// - `<adw-button-row activatable="false">` was honoured, an opt-out libadwaita
|
|
18
|
-
// does not have — and `<adw-action-row activatable="false">` meant the
|
|
19
|
-
// OPPOSITE, because that element reads the attribute by presence.
|
|
6
|
+
// The rules it pins, each of which one renderer had wrong:
|
|
7
|
+
// - `string_is_not_empty` in `adw-action-row.ui` is bound to the TITLE label as much
|
|
8
|
+
// as to the subtitle, so an empty title hides too;
|
|
9
|
+
// - the DOM fires no `change` for a scripted `.checked =`, so a programmatic
|
|
10
|
+
// `switchRow.active = true` must be made to notify anyway — NativeScript's
|
|
11
|
+
// `checkedChange` does fire, and one C source must not give two answers;
|
|
12
|
+
// - `adw_switch_row_init` makes the WHOLE row the switch's activator;
|
|
13
|
+
// - libadwaita has no activatable opt-out for a button row, and
|
|
14
|
+
// `<adw-action-row activatable="false">` reads by PRESENCE, so it means the
|
|
15
|
+
// opposite of what it says.
|
|
20
16
|
import { describe, expect, it } from '@gjsify/unit';
|
|
21
17
|
|
|
22
18
|
import {
|
|
@@ -35,9 +31,8 @@ import type { AdwSwitchRow } from './elements/adw-switch-row.js';
|
|
|
35
31
|
import type { AdwWindowTitle } from './elements/adw-window-title.js';
|
|
36
32
|
|
|
37
33
|
/**
|
|
38
|
-
* Mount an element imperatively
|
|
39
|
-
*
|
|
40
|
-
* value in markup is not a reliable carrier for.
|
|
34
|
+
* Mount an element imperatively, never through parsed HTML: several label vectors hinge
|
|
35
|
+
* on exact whitespace (`' '`, `'\t'`) that markup is not a reliable carrier for.
|
|
41
36
|
*/
|
|
42
37
|
function mount<T extends HTMLElement>(tag: string): { el: T; host: HTMLElement } {
|
|
43
38
|
const host = document.createElement('div');
|
|
@@ -144,13 +139,11 @@ export const AdwActionRowsTest = async () => {
|
|
|
144
139
|
await it('reads `activatable` by PRESENCE, like every other boolean attribute here', () => {
|
|
145
140
|
const { el: row, host } = mount<AdwActionRow>('adw-action-row');
|
|
146
141
|
row.setAttribute('activatable', 'false');
|
|
147
|
-
// The HTML boolean-attribute convention (`<input disabled="false">`
|
|
148
|
-
//
|
|
149
|
-
//
|
|
150
|
-
//
|
|
151
|
-
//
|
|
152
|
-
// reader does not "fix" it into the string comparison
|
|
153
|
-
// <adw-button-row> used to carry, which gave one markup two
|
|
142
|
+
// The HTML boolean-attribute convention (`<input disabled="false">` IS
|
|
143
|
+
// disabled), how every other boolean attribute in this package is read. NOT a
|
|
144
|
+
// libadwaita rule — libadwaita has a typed gboolean and no attribute parsing —
|
|
145
|
+
// so it is pinned here rather than in the cross-renderer vectors, and pinned so
|
|
146
|
+
// nobody "fixes" it into a string comparison that would give one markup two
|
|
154
147
|
// opposite meanings inside one package.
|
|
155
148
|
expect(row.activatable).toBe(true);
|
|
156
149
|
host.remove();
|
|
@@ -183,9 +176,9 @@ export const AdwActionRowsTest = async () => {
|
|
|
183
176
|
|
|
184
177
|
widget.click();
|
|
185
178
|
|
|
186
|
-
// One click,
|
|
187
|
-
//
|
|
188
|
-
//
|
|
179
|
+
// One click, no row activation: GtkListBox does not emit `row-activated` for a
|
|
180
|
+
// click a child widget claimed, and re-clicking the widget here would bounce
|
|
181
|
+
// back into the row's own listener.
|
|
189
182
|
expect(widgetClicks).toBe(1);
|
|
190
183
|
expect(activations.length).toBe(0);
|
|
191
184
|
host.remove();
|
|
@@ -257,16 +250,15 @@ export const AdwActionRowsTest = async () => {
|
|
|
257
250
|
setAttr(row, 'start-icon-name', vector.startIconName);
|
|
258
251
|
setAttr(row, 'end-icon-name', vector.endIconName);
|
|
259
252
|
|
|
260
|
-
// The two `image.icon.{start,end}` nodes are <adw-icon
|
|
261
|
-
//
|
|
253
|
+
// The two `image.icon.{start,end}` nodes are <adw-icon>, not hand-rolled
|
|
254
|
+
// decorative spans.
|
|
262
255
|
const icons = Array.from(row.querySelectorAll('adw-icon')) as HTMLElement[];
|
|
263
256
|
expect(icons.length).toBe(2);
|
|
264
257
|
expect(!icons[0]!.hidden).toBe(vector.startIconVisible);
|
|
265
258
|
expect(!icons[1]!.hidden).toBe(vector.endIconVisible);
|
|
266
|
-
// The mask class is the NORMALIZED name
|
|
267
|
-
//
|
|
268
|
-
//
|
|
269
|
-
// class that has never existed and drew an empty box.
|
|
259
|
+
// The mask class is the NORMALIZED name — the generated classes never
|
|
260
|
+
// carry `-symbolic`, so interpolating the raw one asks for a class that has
|
|
261
|
+
// never existed and draws an empty box.
|
|
270
262
|
if (vector.startIconVisible) {
|
|
271
263
|
expect(icons[0]!.classList.contains(`adw-icon--${normalizeIconName(vector.startIconName)}`)).toBe(
|
|
272
264
|
true,
|
package/src/adw-avatar.spec.ts
CHANGED
|
@@ -1,12 +1,10 @@
|
|
|
1
1
|
// DOM-level conformance tests for <adw-avatar>, driven by the SAME vectors the
|
|
2
2
|
// NativeScript renderer asserts against (`@gjsify/adwaita-core/conformance`).
|
|
3
3
|
//
|
|
4
|
-
//
|
|
5
|
-
//
|
|
6
|
-
//
|
|
7
|
-
//
|
|
8
|
-
// name was painted the wrong colour. Nothing failed, because nothing compared
|
|
9
|
-
// them. This suite is that comparison.
|
|
4
|
+
// Two rules independent copies of the initials + colour derivation drifted from: GTK
|
|
5
|
+
// takes ONE initial from a single-word `text`, and the colour hash runs over UTF-8 BYTES
|
|
6
|
+
// as GLib's does — hashing UTF-16 code units paints every accented name the wrong
|
|
7
|
+
// colour.
|
|
10
8
|
import { describe, expect, it } from '@gjsify/unit';
|
|
11
9
|
|
|
12
10
|
import { AVATAR_MODE_VECTORS } from '@gjsify/adwaita-core/conformance';
|
|
@@ -140,37 +138,70 @@ export const AdwAvatarTest = async () => {
|
|
|
140
138
|
});
|
|
141
139
|
|
|
142
140
|
await describe('<adw-avatar> mode (update_visibility, adw-avatar.c:117)', async () => {
|
|
143
|
-
//
|
|
144
|
-
//
|
|
145
|
-
|
|
146
|
-
|
|
141
|
+
// A 1x1 transparent GIF: a real, decodable image, so `custom-image` is
|
|
142
|
+
// exercised through the same path a photo would take — and no network.
|
|
143
|
+
const PIXEL = 'data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7';
|
|
144
|
+
|
|
147
145
|
for (const { hasCustomImage, showInitials, text, mode, rule } of AVATAR_MODE_VECTORS) {
|
|
148
|
-
|
|
149
|
-
await it(`initials=${showInitials} "${text}" → ${mode} — ${rule}`, () => {
|
|
146
|
+
await it(`image=${hasCustomImage} initials=${showInitials} "${text}" → ${mode} — ${rule}`, () => {
|
|
150
147
|
const host = document.createElement('div');
|
|
151
148
|
document.body.appendChild(host);
|
|
152
149
|
const avatar = document.createElement('adw-avatar');
|
|
153
150
|
if (showInitials) avatar.setAttribute('show-initials', '');
|
|
154
151
|
if (text) avatar.setAttribute('text', text);
|
|
152
|
+
if (hasCustomImage) avatar.setAttribute('custom-image', PIXEL);
|
|
155
153
|
host.appendChild(avatar);
|
|
156
154
|
|
|
157
|
-
// Exactly ONE of the
|
|
158
|
-
//
|
|
155
|
+
// Exactly ONE of the three modes is drawn — the point of the
|
|
156
|
+
// table is the precedence, and "two visible at once" is the
|
|
159
157
|
// failure it guards against.
|
|
160
158
|
const initials = (avatar.querySelector('.adw-avatar-text') as HTMLElement).hidden === false;
|
|
161
159
|
const icon = (avatar.querySelector('.adw-avatar-icon') as HTMLElement).hidden === false;
|
|
160
|
+
const image = (avatar.querySelector('.adw-avatar-custom-image') as HTMLElement).hidden === false;
|
|
162
161
|
expect(initials).toBe(mode === 'initials');
|
|
163
162
|
expect(icon).toBe(mode === 'icon');
|
|
163
|
+
expect(image).toBe(mode === 'image');
|
|
164
164
|
host.remove();
|
|
165
165
|
});
|
|
166
166
|
}
|
|
167
167
|
|
|
168
|
-
await it('
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
//
|
|
172
|
-
|
|
173
|
-
|
|
168
|
+
await it('drops the custom image again when the attribute goes away', () => {
|
|
169
|
+
// The mode is re-derived on every attribute change, so removing the
|
|
170
|
+
// image has to fall back to whichever mode the remaining state picks
|
|
171
|
+
// — this is where a port that only ever ADDS the image node fails.
|
|
172
|
+
const host = document.createElement('div');
|
|
173
|
+
document.body.appendChild(host);
|
|
174
|
+
const avatar = document.createElement('adw-avatar');
|
|
175
|
+
avatar.setAttribute('show-initials', '');
|
|
176
|
+
avatar.setAttribute('text', 'Ada Lovelace');
|
|
177
|
+
avatar.setAttribute('custom-image', PIXEL);
|
|
178
|
+
host.appendChild(avatar);
|
|
179
|
+
|
|
180
|
+
expect((avatar.querySelector('.adw-avatar-custom-image') as HTMLElement).hidden).toBe(false);
|
|
181
|
+
|
|
182
|
+
avatar.removeAttribute('custom-image');
|
|
183
|
+
expect((avatar.querySelector('.adw-avatar-custom-image') as HTMLElement).hidden).toBe(true);
|
|
184
|
+
expect((avatar.querySelector('.adw-avatar-text') as HTMLElement).hidden).toBe(false);
|
|
185
|
+
|
|
186
|
+
host.remove();
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
await it('fills the circle with the image rather than letterboxing it', () => {
|
|
190
|
+
// `object-fit` is the whole reason the image looks like an avatar and
|
|
191
|
+
// not like a photo in a round frame — a class-name assertion cannot
|
|
192
|
+
// see it, and it is one word away from being wrong.
|
|
193
|
+
const host = document.createElement('div');
|
|
194
|
+
document.body.appendChild(host);
|
|
195
|
+
const avatar = document.createElement('adw-avatar');
|
|
196
|
+
avatar.setAttribute('custom-image', PIXEL);
|
|
197
|
+
avatar.setAttribute('size', '64');
|
|
198
|
+
host.appendChild(avatar);
|
|
199
|
+
|
|
200
|
+
const image = avatar.querySelector('.adw-avatar-custom-image') as HTMLElement;
|
|
201
|
+
expect(getComputedStyle(image).objectFit).toBe('cover');
|
|
202
|
+
expect(Math.round(image.getBoundingClientRect().width)).toBe(64);
|
|
203
|
+
|
|
204
|
+
host.remove();
|
|
174
205
|
});
|
|
175
206
|
});
|
|
176
207
|
};
|
package/src/adw-banner.spec.ts
CHANGED
|
@@ -1,13 +1,10 @@
|
|
|
1
1
|
// DOM-level conformance tests for <adw-banner>, driven by the SAME vectors the
|
|
2
2
|
// NativeScript renderer asserts against (`@gjsify/adwaita-core/conformance`).
|
|
3
3
|
//
|
|
4
|
-
// The
|
|
5
|
-
//
|
|
6
|
-
//
|
|
7
|
-
//
|
|
8
|
-
// TRUE, adw-banner.c:422-425), `button-style` was in neither, and the mnemonic
|
|
9
|
-
// marker the template pins onto the button (adw-banner.ui:33) was painted as a
|
|
10
|
-
// literal underscore. Nothing failed, because nothing compared them.
|
|
4
|
+
// The defaults independent banner logic drifted from, all of them in
|
|
5
|
+
// `ADW_BANNER_DEFAULTS`: `revealed` is FALSE, `use-markup` is TRUE (opting it in inverts
|
|
6
|
+
// the C), `button-style` exists, and the mnemonic marker the template pins onto the
|
|
7
|
+
// button is a marker, not a literal underscore to paint.
|
|
11
8
|
import { describe, expect, it } from '@gjsify/unit';
|
|
12
9
|
|
|
13
10
|
import { ADW_BANNER_DEFAULTS } from '@gjsify/adwaita-core';
|
|
@@ -64,7 +61,7 @@ export const AdwBannerTest = async () => {
|
|
|
64
61
|
});
|
|
65
62
|
|
|
66
63
|
await describe('adw-banner use-markup — a DELIBERATE departure from the C default', async () => {
|
|
67
|
-
// The C default is unambiguously TRUE
|
|
64
|
+
// The C default is unambiguously TRUE and core
|
|
68
65
|
// carries it as the spec value. This renderer does NOT adopt it: Pango
|
|
69
66
|
// markup is not HTML, and defaulting a `title` attribute to `innerHTML`
|
|
70
67
|
// would make an injection sink out of a widget that is not one in GTK.
|
|
@@ -2,15 +2,12 @@
|
|
|
2
2
|
// vectors the NativeScript renderer asserts against
|
|
3
3
|
// (`@gjsify/adwaita-core/conformance`).
|
|
4
4
|
//
|
|
5
|
-
//
|
|
6
|
-
// `image-text-button` on the button hosting it
|
|
7
|
-
//
|
|
8
|
-
//
|
|
9
|
-
//
|
|
10
|
-
//
|
|
11
|
-
// was drawn with the wrong horizontal padding for its whole life. `use-underline`
|
|
12
|
-
// was absent, and an empty `icon-name` hid the image where the C sets
|
|
13
|
-
// `image-missing` (:355-356).
|
|
5
|
+
// The STYLE CLASS is why this suite exists: `AdwButtonContent` puts
|
|
6
|
+
// `image-text-button` on the button hosting it and removes it on unroot, and
|
|
7
|
+
// `_buttons.scss` gives that class 9px horizontal padding where a plain text button has
|
|
8
|
+
// 17px — so a renderer that never sets the class draws every icon+label button with the
|
|
9
|
+
// wrong padding. Also pinned: `use-underline`, and that an empty `icon-name` shows
|
|
10
|
+
// `image-missing` rather than hiding the image.
|
|
14
11
|
import { describe, expect, it } from '@gjsify/unit';
|
|
15
12
|
|
|
16
13
|
import { BUTTON_CONTENT_DEFAULT_VECTORS } from '@gjsify/adwaita-core/conformance';
|
|
@@ -93,8 +90,8 @@ export const AdwButtonContentTest = async () => {
|
|
|
93
90
|
host.appendChild(split);
|
|
94
91
|
|
|
95
92
|
// The class goes on the `splitbutton` node, which is what
|
|
96
|
-
// `splitbutton.image-text-button > button`
|
|
97
|
-
//
|
|
93
|
+
// `splitbutton.image-text-button > button` selects — a different declaration
|
|
94
|
+
// block from the plain-button one.
|
|
98
95
|
expect(split.classList.contains(BUTTON_CONTENT_STYLE_CLASS)).toBe(true);
|
|
99
96
|
expect(action.classList.contains(BUTTON_CONTENT_STYLE_CLASS)).toBe(false);
|
|
100
97
|
// …and the padding lands on the INNER button, which is what that
|
|
@@ -134,7 +131,7 @@ export const AdwButtonContentTest = async () => {
|
|
|
134
131
|
maskName === '' ? 0 : 1,
|
|
135
132
|
);
|
|
136
133
|
// The image node is never hidden — `gtk_widget_set_visible` is
|
|
137
|
-
// called on the LABEL only
|
|
134
|
+
// called on the LABEL only.
|
|
138
135
|
expect(icon.hidden).toBe(false);
|
|
139
136
|
host.remove();
|
|
140
137
|
});
|
|
@@ -36,17 +36,15 @@ export const AdwButtonRowTest = async () => {
|
|
|
36
36
|
|
|
37
37
|
expect(errors.length).toBe(0);
|
|
38
38
|
expect(row.textContent).toContain('Add account');
|
|
39
|
-
// The start icon span carries the adw-icon class derived from the name.
|
|
40
39
|
expect(row.querySelector('.adw-icon--list-add') !== null).toBe(true);
|
|
41
|
-
// Activatable by default.
|
|
42
40
|
expect(row.classList.contains('activatable')).toBe(true);
|
|
43
41
|
host.remove();
|
|
44
42
|
});
|
|
45
43
|
|
|
46
44
|
// DELETED: `honours activatable="false"`. It was green, and what it
|
|
47
45
|
// pinned was an INVENTION — `Adw.ButtonRow` is always activatable
|
|
48
|
-
// (`<property name="activatable">True</property>`, adw-button-row.ui
|
|
49
|
-
// "AdwButtonRow is always activatable."
|
|
46
|
+
// (`<property name="activatable">True</property>`, adw-button-row.ui;
|
|
47
|
+
// "AdwButtonRow is always activatable."), and the
|
|
50
48
|
// class exposes no property, no setter and no getter for it. The opt-out
|
|
51
49
|
// also gave one markup two opposite meanings inside one package, because
|
|
52
50
|
// <adw-action-row> reads `activatable` by PRESENCE — so
|