@gjsify/adwaita-web 0.64.2 → 0.64.3-next.260

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gjsify/adwaita-web",
3
- "version": "0.64.2",
3
+ "version": "0.64.3-next.260",
4
4
  "description": "Adwaita/Libadwaita web components for browser targets",
5
5
  "type": "module",
6
6
  "main": "./src/index.ts",
@@ -58,14 +58,14 @@
58
58
  "@codemirror/language": "^6.12.4",
59
59
  "@codemirror/state": "^6.7.0",
60
60
  "@codemirror/view": "^6.43.4",
61
- "@gjsify/adwaita-core": "^0.64.2",
62
- "@gjsify/adwaita-fonts": "^0.64.2",
63
- "@gjsify/adwaita-icons": "^0.64.2",
64
- "@gjsify/gtksource-core": "^0.64.2",
61
+ "@gjsify/adwaita-core": "0.64.3-next.260",
62
+ "@gjsify/adwaita-fonts": "0.64.3-next.260",
63
+ "@gjsify/adwaita-icons": "0.64.3-next.260",
64
+ "@gjsify/gtksource-core": "0.64.3-next.260",
65
65
  "@lezer/highlight": "^1.2.3"
66
66
  },
67
67
  "peerDependencies": {
68
- "@gjsify/app-settings": "^0.64.2"
68
+ "@gjsify/app-settings": "0.64.3-next.260"
69
69
  },
70
70
  "peerDependenciesMeta": {
71
71
  "@gjsify/app-settings": {
@@ -73,11 +73,11 @@
73
73
  }
74
74
  },
75
75
  "devDependencies": {
76
- "@gjsify/app-settings": "^0.64.2",
77
- "@gjsify/blueprint": "^0.64.2",
78
- "@gjsify/cli": "^0.64.2",
79
- "@gjsify/unit": "^0.64.2",
80
- "@gjsify/vite-plugin-blueprint": "^0.64.2",
76
+ "@gjsify/app-settings": "0.64.3-next.260",
77
+ "@gjsify/blueprint": "0.64.3-next.260",
78
+ "@gjsify/cli": "0.64.3-next.260",
79
+ "@gjsify/unit": "0.64.3-next.260",
80
+ "@gjsify/vite-plugin-blueprint": "0.64.3-next.260",
81
81
  "sass": "^1.101.0",
82
82
  "typescript": "^6.0.3"
83
83
  },
@@ -114,5 +114,6 @@
114
114
  "bugs": {
115
115
  "url": "https://github.com/gjsify/gjsify/issues"
116
116
  },
117
- "homepage": "https://github.com/gjsify/gjsify/tree/main/packages/web/adwaita-web#readme"
117
+ "homepage": "https://github.com/gjsify/gjsify/tree/main/packages/web/adwaita-web#readme",
118
+ "gitHead": "886de21be840429ce043cdbf8195172530e230d4"
118
119
  }
@@ -199,8 +199,9 @@ adw-about-dialog {
199
199
  }
200
200
  }
201
201
 
202
- // The trailing chevron / link affordance on a navigation or link row.
203
- .adw-about-dialog-chevron {
202
+ // The trailing chevron on a navigation row, and the external-link / mail glyph on a link row.
203
+ .adw-about-dialog-chevron,
204
+ .adw-about-dialog-link-icon {
204
205
  width: 16px;
205
206
  height: 16px;
206
207
  opacity: var(--dim-opacity);
@@ -265,8 +266,15 @@ adw-about-dialog {
265
266
  text-align: center;
266
267
  }
267
268
 
269
+ .adw-about-dialog-legal-title {
270
+ margin: var(--spacing-m) 0 0;
271
+ font-weight: 700;
272
+ text-align: center;
273
+ }
274
+
268
275
  .adw-about-dialog-license {
269
276
  margin: var(--spacing-s) 0 0;
277
+ white-space: pre-wrap;
270
278
  font-size: var(--font-size-small);
271
279
  opacity: var(--dim-opacity);
272
280
  text-align: center;
@@ -46,6 +46,8 @@
46
46
  --icon-view-more: url("data:image/svg+xml,%3Csvg%20height%3D'16px'%20viewBox%3D'0%200%2016%2016'%20width%3D'16px'%20xmlns%3D'http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg'%3E%20%3Cpath%20d%3D'm%207.996094%200%20c%20-1.105469%200%20-2%200.894531%20-2%202%20s%200.894531%202%202%202%20c%201.101562%200%202%20-0.894531%202%20-2%20s%20-0.898438%20-2%20-2%20-2%20z%20m%200%206%20c%20-1.105469%200%20-2%200.894531%20-2%202%20s%200.894531%202%202%202%20c%201.101562%200%202%20-0.894531%202%20-2%20s%20-0.898438%20-2%20-2%20-2%20z%20m%200%206%20c%20-1.105469%200%20-2%200.894531%20-2%202%20s%200.894531%202%202%202%20c%201.101562%200%202%20-0.894531%202%20-2%20s%20-0.898438%20-2%20-2%20-2%20z%20m%200%200'%20fill%3D'currentColor'%2F%3E%20%3C%2Fsvg%3E");
47
47
  --icon-view-paged: url("data:image/svg+xml,%3Csvg%20height%3D'16px'%20viewBox%3D'0%200%2016%2016'%20width%3D'16px'%20xmlns%3D'http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg'%3E%20%3Cg%20fill%3D'currentColor'%3E%20%3Cpath%20d%3D'm%209%205.5%20v%20-1%20c%200%20-0.277344%20-0.222656%20-0.5%20-0.5%20-0.5%20h%20-3%20c%20-0.277344%200%20-0.5%200.222656%20-0.5%200.5%20v%201%20c%200%200.277344%200.222656%200.5%200.5%200.5%20h%203%20c%200.277344%200%200.5%20-0.222656%200.5%20-0.5%20z%20m%200%200'%20fill-opacity%3D'0.34902'%2F%3E%20%3Cpath%20d%3D'm%204%200%20c%20-1.644531%200%20-3%201.355469%20-3%203%20v%207%20c%200%201.644531%201.355469%203%203%203%20h%206%20c%201.644531%200%203%20-1.355469%203%20-3%20v%20-7%20c%200%20-1.644531%20-1.355469%20-3%20-3%20-3%20z%20m%200%202%20h%206%20c%200.570312%200%201%200.429688%201%201%20v%207%20c%200%200.570312%20-0.429688%201%20-1%201%20h%20-6%20c%20-0.570312%200%20-1%20-0.429688%20-1%20-1%20v%20-7%20c%200%20-0.570312%200.429688%20-1%201%20-1%20z%20m%2011%202%20c%20-0.550781%200%20-1%200.449219%20-1%201%20v%208%20c%200%200.550781%20-0.449219%201%20-1%201%20h%20-7%20c%20-0.550781%200%20-1%200.449219%20-1%201%20s%200.449219%201%201%201%20h%207%20c%201.65625%200%203%20-1.34375%203%20-3%20v%20-8%20c%200%20-0.550781%20-0.449219%20-1%20-1%20-1%20z%20m%200%200'%2F%3E%20%3Cpath%20d%3D'm%209%208.5%20v%20-1%20c%200%20-0.277344%20-0.222656%20-0.5%20-0.5%20-0.5%20h%20-3%20c%20-0.277344%200%20-0.5%200.222656%20-0.5%200.5%20v%201%20c%200%200.277344%200.222656%200.5%200.5%200.5%20h%203%20c%200.277344%200%200.5%20-0.222656%200.5%20-0.5%20z%20m%200%200'%20fill-opacity%3D'0.34902'%2F%3E%20%3C%2Fg%3E%20%3C%2Fsvg%3E");
48
48
  --icon-view-columns: url("data:image/svg+xml,%3Csvg%20height%3D'16px'%20viewBox%3D'0%200%2016%2016'%20width%3D'16px'%20xmlns%3D'http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg'%3E%20%3Cpath%20d%3D'm%202%201%20c%20-0.554688%200%20-1%200.445312%20-1%201%20v%2012%20c%200%200.554688%200.445312%201%201%201%20h%202%20c%200.554688%200%201%20-0.445312%201%20-1%20v%20-12%20c%200%20-0.554688%20-0.445312%20-1%20-1%20-1%20z%20m%205%200%20c%20-0.554688%200%20-1%200.445312%20-1%201%20v%2012%20c%200%200.554688%200.445312%201%201%201%20h%202%20c%200.554688%200%201%20-0.445312%201%20-1%20v%20-12%20c%200%20-0.554688%20-0.445312%20-1%20-1%20-1%20z%20m%205%200%20c%20-0.554688%200%20-1%200.445312%20-1%201%20v%2012%20c%200%200.554688%200.445312%201%201%201%20h%202%20c%200.554688%200%201%20-0.445312%201%20-1%20v%20-12%20c%200%20-0.554688%20-0.445312%20-1%20-1%20-1%20z%20m%200%200'%20fill%3D'currentColor'%2F%3E%20%3C%2Fsvg%3E");
49
+ --icon-adw-external-link: url("data:image/svg+xml,%3Csvg%20height%3D'16px'%20viewBox%3D'0%200%2016%2016'%20width%3D'16px'%20xmlns%3D'http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg'%3E%20%3Cpath%20d%3D'm%209%201%20h%206%20v%206%20h%20-2%20v%20-2.5%20l%20-4.5%204.5%20l%20-1.5%20-1.5%20l%204.5%20-4.5%20h%20-2.5%20z%20m%20-6%202%20h%204%20v%202%20h%20-3%20v%208%20h%208%20v%20-3%20h%202%20v%204%20c%200%200.55%20-0.45%201%20-1%201%20h%20-10%20c%20-0.55%200%20-1%20-0.45%20-1%20-1%20v%20-10%20c%200%20-0.55%200.45%20-1%201%20-1%20z'%20fill%3D'currentColor'%2F%3E%20%3C%2Fsvg%3E");
50
+ --icon-adw-mail-send: url("data:image/svg+xml,%3Csvg%20height%3D'16px'%20viewBox%3D'0%200%2016%2016'%20width%3D'16px'%20xmlns%3D'http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg'%3E%20%3Cpath%20d%3D'm%202%203%20h%2012%20c%200.55%200%201%200.45%201%201%20v%208%20c%200%200.55%20-0.45%201%20-1%201%20h%20-12%20c%20-0.55%200%20-1%20-0.45%20-1%20-1%20v%20-8%20c%200%20-0.55%200.45%20-1%201%20-1%20z%20m%201.5%202%20l%204.5%203.5%20l%204.5%20-3.5%20z%20m%20-0.5%201.7%20v%205.3%20h%2010%20v%20-5.3%20l%20-5%203.9%20z'%20fill%3D'currentColor'%20fill-rule%3D'evenodd'%2F%3E%20%3C%2Fsvg%3E");
49
51
  --icon-list-remove: url("data:image/svg+xml,%3Csvg%20height%3D'16px'%20viewBox%3D'0%200%2016%2016'%20width%3D'16px'%20xmlns%3D'http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg'%3E%20%3Cpath%20d%3D'm%201%207%20h%2014%20v%202%20h%20-14%20z%20m%200%200'%20fill%3D'currentColor'%2F%3E%20%3C%2Fsvg%3E");
50
52
  --icon-send-to: url("data:image/svg+xml,%3Csvg%20height%3D'16px'%20viewBox%3D'0%200%2016%2016'%20width%3D'16px'%20xmlns%3D'http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg'%3E%20%3Cpath%20d%3D'm%208%200%20c%20-0.257812%200%20-0.511719%200.0976562%20-0.707031%200.292969%20l%20-1.246094%201.25%20l%20-1.765625%201.730469%20c%20-0.191406%200.199218%20-0.28125%200.441406%20-0.28125%200.75%20c%200%200.976562%201%201%201%201%20c%200.257812%200%200.527344%20-0.128907%200.71875%20-0.3125%20l%201.28125%20-1.28125%20v%207.570312%20s%200%201%201%201%20s%201%20-1%201%20-1%20v%20-7.570312%20l%201.28125%201.28125%20c%200.191406%200.183593%200.410156%200.3125%200.71875%200.3125%20c%200%200%201%20-0.023438%201%20-1%20c%200%20-0.308594%20-0.089844%20-0.550782%20-0.28125%20-0.75%20l%20-1.765625%20-1.730469%20l%20-1.246094%20-1.25%20c%20-0.195312%20-0.1953128%20-0.449219%20-0.292969%20-0.707031%20-0.292969%20z%20m%20-3.96875%206.964844%20c%20-1.660156%20-0.027344%20-3.03125%201.371094%20-3.03125%203.035156%20v%202.972656%20c%200%201.664063%201.371094%203.035156%203.03125%203.035156%20h%207.9375%20c%201.664062%200%203.03125%20-1.371093%203.03125%20-3.035156%20v%20-2.972656%20c%200%20-1.664062%20-1.367188%20-3.0625%20-3.03125%20-3.035156%20l%20-1.96875%200.035156%20v%202%20l%201.96875%20-0.035156%20c%200.589844%20-0.007813%201.03125%200.445312%201.03125%201.035156%20v%202.972656%20c%200%200.589844%20-0.441406%201.035156%20-1.03125%201.035156%20h%20-7.9375%20c%20-0.585938%200%20-1.03125%20-0.445312%20-1.03125%20-1.035156%20v%20-2.972656%20c%200%20-0.589844%200.445312%20-1.042969%201.03125%20-1.035156%20l%201.96875%200.035156%20v%20-2%20z%20m%200%200'%20fill%3D'currentColor'%2F%3E%20%3C%2Fsvg%3E");
51
53
  --icon-folder-music: url("data:image/svg+xml,%3Csvg%20height%3D'16px'%20viewBox%3D'0%200%2016%2016'%20width%3D'16px'%20xmlns%3D'http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg'%3E%20%3Cpath%20d%3D'm%208%201%20c%20-1.644531%200%20-3%201.355469%20-3%203%20v%204.339844%20c%20-0.457031%20-0.214844%20-0.964844%20-0.339844%20-1.5%20-0.339844%20c%20-1.921875%200%20-3.5%201.578125%20-3.5%203.5%20s%201.578125%203.5%203.5%203.5%20c%201.914062%200%203.492188%20-1.570312%203.5%20-3.484375%20c%200%20-0.003906%200%20-0.011719%200%20-0.015625%20v%20-7.5%20c%200%20-0.5625%200.4375%20-1%201%20-1%20h%204%20c%200.5625%200%201%200.4375%201%201%20v%204.339844%20c%20-0.457031%20-0.214844%20-0.964844%20-0.339844%20-1.5%20-0.339844%20c%20-1.921875%200%20-3.5%201.578125%20-3.5%203.5%20s%201.578125%203.5%203.5%203.5%20s%203.5%20-1.578125%203.5%20-3.5%20v%20-7.5%20c%200%20-1.644531%20-1.355469%20-3%20-3%20-3%20z%20m%20-4.5%209%20c%200.839844%200%201.5%200.660156%201.5%201.5%20v%200.015625%20c%20-0.007812%200.832031%20-0.664062%201.484375%20-1.5%201.484375%20c%20-0.839844%200%20-1.5%20-0.660156%20-1.5%20-1.5%20s%200.660156%20-1.5%201.5%20-1.5%20z%20m%208%200%20c%200.839844%200%201.5%200.660156%201.5%201.5%20s%20-0.660156%201.5%20-1.5%201.5%20s%20-1.5%20-0.660156%20-1.5%20-1.5%20s%200.660156%20-1.5%201.5%20-1.5%20z%20m%200%200'%20fill%3D'currentColor'%2F%3E%20%3C%2Fsvg%3E");
@@ -297,6 +299,16 @@
297
299
  -webkit-mask-image: var(--icon-view-columns);
298
300
  }
299
301
 
302
+ .adw-icon--adw-external-link {
303
+ mask-image: var(--icon-adw-external-link);
304
+ -webkit-mask-image: var(--icon-adw-external-link);
305
+ }
306
+
307
+ .adw-icon--adw-mail-send {
308
+ mask-image: var(--icon-adw-mail-send);
309
+ -webkit-mask-image: var(--icon-adw-mail-send);
310
+ }
311
+
300
312
  .adw-icon--list-remove {
301
313
  mask-image: var(--icon-list-remove);
302
314
  -webkit-mask-image: var(--icon-list-remove);
@@ -7,7 +7,13 @@
7
7
  // and `website` is the one input the Details-visibility predicate leaves OUT.
8
8
  import { describe, expect, it } from '@gjsify/unit';
9
9
 
10
- import { ADW_ABOUT_DIALOG_LABELS, type ReleaseNotesSegment, type Toast, stripMnemonic } from '@gjsify/adwaita-core';
10
+ import {
11
+ ADW_ABOUT_DIALOG_LABELS,
12
+ type ReleaseNotesSegment,
13
+ type Toast,
14
+ licenseTypeForNick,
15
+ stripMnemonic,
16
+ } from '@gjsify/adwaita-core';
11
17
  import {
12
18
  ABOUT_DIALOG_CREDITS_LEGAL_VECTORS,
13
19
  ABOUT_DIALOG_DETAILS_VECTORS,
@@ -15,9 +21,13 @@ import {
15
21
  ABOUT_DIALOG_HEADER_VECTORS,
16
22
  ABOUT_DIALOG_SUPPORT_VECTORS,
17
23
  type AboutDialogDoorLike,
24
+ ACKNOWLEDGEMENT_SECTIONS_VECTORS,
18
25
  CREDITS_SECTIONS_VECTORS,
19
26
  CREDIT_PERSON_VECTORS,
27
+ LEGAL_SECTIONS_VECTORS,
28
+ LICENSE_SETTER_VECTORS,
20
29
  RELEASE_NOTES_VECTORS,
30
+ TRANSLATOR_CREDITS_VECTORS,
21
31
  driveAboutDialogDoorVectors,
22
32
  driveReleaseNotesVectors,
23
33
  } from '@gjsify/adwaita-core/conformance';
@@ -27,7 +37,25 @@ const label = (key: keyof typeof ADW_ABOUT_DIALOG_LABELS) => stripMnemonic(ADW_A
27
37
 
28
38
  interface Mounted {
29
39
  dialog: HTMLElement &
30
- AboutDialogDoorLike & { developers: string[]; designers: string[]; artists: string[]; documenters: string[] };
40
+ AboutDialogDoorLike & {
41
+ developers: string[];
42
+ designers: string[];
43
+ artists: string[];
44
+ documenters: string[];
45
+ copyright: string;
46
+ license: string;
47
+ licenseType: number;
48
+ licenseUrl: string;
49
+ translatorCredits: string;
50
+ add_credit_section(name: string | null, people: string[]): void;
51
+ add_acknowledgement_section(name: string | null, people: string[]): void;
52
+ add_legal_section(
53
+ title: string,
54
+ copyright: string | null,
55
+ licenseType: number,
56
+ license: string | null,
57
+ ): void;
58
+ };
31
59
  host: HTMLElement;
32
60
  /** Every `window.open` the dialog performed, most recent last. */
33
61
  opened: string[];
@@ -135,18 +163,18 @@ export const AdwAboutDialogTest = async () => {
135
163
 
136
164
  await describe('adw-about-dialog credits page (update_credits vectors)', async () => {
137
165
  for (const { input, sections, rule } of CREDITS_SECTIONS_VECTORS) {
138
- // `add_credit_section` has no counterpart on this element yet, so the
139
- // rows exercising it are asserted by the core suite alone.
140
- if (input.creditSections) continue;
141
- // Same for `translator-credits`: no property, so no "Translated by".
142
- if (input.translatorCredits !== undefined) continue;
143
-
144
166
  await it(rule, () => {
145
167
  const mounted = mount();
146
168
  mounted.dialog.developers = [...(input.developers ?? [])];
147
169
  mounted.dialog.designers = [...(input.designers ?? [])];
148
170
  mounted.dialog.artists = [...(input.artists ?? [])];
149
171
  mounted.dialog.documenters = [...(input.documenters ?? [])];
172
+ if (input.translatorCredits !== undefined && input.translatorCredits !== null) {
173
+ mounted.dialog.translatorCredits = input.translatorCredits;
174
+ }
175
+ for (const section of input.creditSections ?? []) {
176
+ mounted.dialog.add_credit_section(section.title, [...section.people]);
177
+ }
150
178
 
151
179
  const credits = page(mounted.dialog, 'credits');
152
180
  const groups = credits ? [...credits.querySelectorAll('adw-preferences-group')] : [];
@@ -254,25 +282,27 @@ export const AdwAboutDialogTest = async () => {
254
282
  });
255
283
 
256
284
  await describe('adw-about-dialog main page (credits/legal vectors)', async () => {
257
- // No acknowledgements API on this element; the core suite covers it.
258
- for (const vector of ABOUT_DIALOG_CREDITS_LEGAL_VECTORS.filter((row) => !row.hasAcknowledgements)) {
259
- const { hasCredits, hasLegal, visible, rule } = vector;
260
- await it(`credits=${hasCredits} legal=${hasLegal} — ${rule}`, () => {
285
+ for (const vector of ABOUT_DIALOG_CREDITS_LEGAL_VECTORS) {
286
+ const { hasCredits, hasLegal, hasAcknowledgements, visible, rule } = vector;
287
+ await it(`credits=${hasCredits} legal=${hasLegal} ack=${hasAcknowledgements} — ${rule}`, () => {
261
288
  const mounted = mount({
262
289
  'application-name': 'Builder',
263
290
  ...(hasLegal ? { copyright: '© 2026 Ada' } : {}),
264
291
  });
265
292
  if (hasCredits) mounted.dialog.developers = ['Ada'];
293
+ if (hasAcknowledgements) mounted.dialog.add_acknowledgement_section('Funded by', ['Bob']);
266
294
 
267
295
  const main = page(mounted.dialog, 'main');
268
296
  expect({
269
297
  creditsLegalGroup: group(mounted.dialog, 'credits-legal') !== null,
270
298
  creditsRow: hasRow(main, label('creditsRow')),
271
299
  legalRow: hasRow(main, label('legalRow')),
300
+ acknowledgementsRow: hasRow(main, label('acknowledgementsRow')),
272
301
  }).toStrictEqual({
273
302
  creditsLegalGroup: visible.creditsLegalGroup,
274
303
  creditsRow: visible.creditsRow,
275
304
  legalRow: visible.legalRow,
305
+ acknowledgementsRow: visible.acknowledgementsRow,
276
306
  });
277
307
 
278
308
  mounted.dispose();
@@ -280,6 +310,162 @@ export const AdwAboutDialogTest = async () => {
280
310
  }
281
311
  });
282
312
 
313
+ await describe('adw-about-dialog translator-credits (split vectors)', async () => {
314
+ for (const { value, people, rule } of TRANSLATOR_CREDITS_VECTORS) {
315
+ await it(`${JSON.stringify(value)} — ${rule}`, () => {
316
+ const mounted = mount();
317
+ if (value !== null) mounted.dialog.translatorCredits = value;
318
+ const groups = [...(page(mounted.dialog, 'credits')?.querySelectorAll('adw-preferences-group') ?? [])];
319
+ if (people.length === 0) {
320
+ expect(groups).toHaveLength(0);
321
+ } else {
322
+ expect(groups).toHaveLength(1);
323
+ expect(groups[0]!.getAttribute('title')).toBe('Translated by');
324
+ expect(rowTitles(groups[0]!)).toStrictEqual([...people]);
325
+ }
326
+ mounted.dispose();
327
+ });
328
+ }
329
+ });
330
+
331
+ await describe('adw-about-dialog acknowledgements page (add_acknowledgement_section vectors)', async () => {
332
+ for (const { sections, groups, rule } of ACKNOWLEDGEMENT_SECTIONS_VECTORS) {
333
+ await it(rule, () => {
334
+ const mounted = mount();
335
+ for (const section of sections) {
336
+ mounted.dialog.add_acknowledgement_section(section.title, [...section.people]);
337
+ }
338
+ const nodes = [
339
+ ...(page(mounted.dialog, 'acknowledgements')?.querySelectorAll('adw-preferences-group') ?? []),
340
+ ];
341
+ expect(nodes.map((node) => node.getAttribute('title'))).toStrictEqual(groups.map((g) => g.title));
342
+ for (const [index, expected] of groups.entries()) {
343
+ const rows = [...nodes[index]!.querySelectorAll('adw-action-row')];
344
+ expect(rows.map((row) => row.getAttribute('title'))).toStrictEqual(
345
+ expected.people.map((person) => person.name),
346
+ );
347
+ for (const [at, person] of expected.people.entries()) {
348
+ expect(activateRow(mounted.dialog, rows[at]!)).toBe(person.uri);
349
+ }
350
+ }
351
+ mounted.dispose();
352
+ });
353
+ }
354
+ });
355
+
356
+ await describe('adw-about-dialog license and legal page (update_legal vectors)', async () => {
357
+ for (const vector of LEGAL_SECTIONS_VECTORS) {
358
+ await it(vector.rule, () => {
359
+ const mounted = mount();
360
+ mounted.dialog.copyright = vector.copyright;
361
+ mounted.dialog.licenseType = vector.licenseType;
362
+ if (vector.license.length > 0 && vector.licenseType === 1) mounted.dialog.license = vector.license;
363
+ for (const section of vector.legalSections ?? []) {
364
+ mounted.dialog.add_legal_section(
365
+ section.title,
366
+ section.copyright,
367
+ section.licenseType,
368
+ section.license,
369
+ );
370
+ }
371
+
372
+ expect(hasRow(page(mounted.dialog, 'main'), label('legalRow'))).toBe(vector.hasLegal);
373
+ const legal = page(mounted.dialog, 'legal');
374
+ if (!vector.hasLegal) {
375
+ expect(legal).toBe(null);
376
+ } else {
377
+ const body = legal!;
378
+ const titles = [...body.querySelectorAll('.adw-about-dialog-legal-title')].map(
379
+ (node) => node.textContent,
380
+ );
381
+ expect(titles).toStrictEqual(
382
+ vector.sections.filter((s) => s.title !== null).map((section) => section.title),
383
+ );
384
+ const texts = [...body.querySelectorAll('.adw-about-dialog-license')].map(
385
+ (node) => node.textContent,
386
+ );
387
+ expect(texts).toStrictEqual(
388
+ vector.sections.filter((s) => s.markup.length > 0).map((section) => section.text),
389
+ );
390
+ }
391
+ mounted.dispose();
392
+ });
393
+ }
394
+
395
+ await it('turns the warranty line link into a cancelable activate-link', () => {
396
+ const mounted = mount();
397
+ mounted.dialog.licenseType = 7;
398
+ const anchor = page(mounted.dialog, 'legal')!.querySelector('a')!;
399
+ expect(anchor.textContent).toBe('The MIT License (MIT)');
400
+ mounted.dialog.addEventListener('activate-link', (event) => event.preventDefault());
401
+ anchor.click();
402
+ expect(mounted.opened).toStrictEqual([]);
403
+ mounted.dispose();
404
+ });
405
+
406
+ await it('opens a warranty line link when nothing handled it', () => {
407
+ const mounted = mount();
408
+ mounted.dialog.licenseType = 7;
409
+ page(mounted.dialog, 'legal')!.querySelector('a')!.click();
410
+ expect(mounted.opened).toStrictEqual(['https://opensource.org/licenses/mit-license.php']);
411
+ mounted.dispose();
412
+ });
413
+
414
+ await it('links a custom license to license-url (not in libadwaita)', () => {
415
+ const mounted = mount();
416
+ mounted.dialog.license = 'Proprietary';
417
+ mounted.dialog.licenseUrl = 'https://example.org/license';
418
+ const anchor = page(mounted.dialog, 'legal')!.querySelector('a')!;
419
+ expect(anchor.textContent).toBe('Proprietary');
420
+ anchor.click();
421
+ expect(mounted.opened).toStrictEqual(['https://example.org/license']);
422
+ mounted.dispose();
423
+ });
424
+ });
425
+
426
+ await describe('adw-about-dialog license / license-type (setter vectors)', async () => {
427
+ for (const { steps, license, licenseType, notify, rule } of LICENSE_SETTER_VECTORS) {
428
+ await it(rule, () => {
429
+ const mounted = mount();
430
+ const seen: string[] = [];
431
+ for (const name of ['license', 'license-type']) {
432
+ mounted.dialog.addEventListener(`notify::${name}`, () => seen.push(name));
433
+ }
434
+ for (const step of steps) {
435
+ if (step.property === 'license') mounted.dialog.license = String(step.value);
436
+ else mounted.dialog.licenseType = Number(step.value);
437
+ }
438
+ expect(mounted.dialog.license).toBe(license);
439
+ expect(mounted.dialog.licenseType).toBe(licenseType);
440
+ expect(seen).toStrictEqual([...notify]);
441
+ mounted.dispose();
442
+ });
443
+ }
444
+
445
+ await it('takes a Gtk.License nick as the license-type attribute and keeps it', () => {
446
+ const mounted = mount({ 'license-type': 'mit-x11' });
447
+ expect(mounted.dialog.licenseType).toBe(licenseTypeForNick('mit-x11'));
448
+ expect(mounted.dialog.getAttribute('license-type')).toBe('mit-x11');
449
+ mounted.dispose();
450
+ });
451
+
452
+ await it('refuses an unknown nick and writes the real state back', () => {
453
+ const mounted = mount({ 'license-type': 'not-a-license' });
454
+ expect(mounted.dialog.licenseType).toBe(0);
455
+ expect(mounted.dialog.hasAttribute('license-type')).toBe(false);
456
+ mounted.dispose();
457
+ });
458
+
459
+ await it('honours license attributes set before the element connects', () => {
460
+ const dialog = document.createElement('adw-about-dialog') as Mounted['dialog'];
461
+ dialog.setAttribute('license', 'Early');
462
+ document.body.appendChild(dialog);
463
+ expect(dialog.license).toBe('Early');
464
+ expect(dialog.licenseType).toBe(1);
465
+ dialog.remove();
466
+ });
467
+ });
468
+
283
469
  await describe('adw-about-dialog header fields (no invented fallbacks)', async () => {
284
470
  for (const { property, value, visible, rule } of ABOUT_DIALOG_HEADER_VECTORS) {
285
471
  if (property === 'application-icon' || property === 'application-name') {
@@ -437,4 +623,59 @@ export const AdwAboutDialogTest = async () => {
437
623
  mounted.dispose();
438
624
  });
439
625
  });
626
+
627
+ await describe('adw-about-dialog link rows are AdwLinkRow, navigation rows are not', async () => {
628
+ const iconOf = (row: Element) => row.querySelector('gtk-image');
629
+
630
+ await it('draws website, support, issue and credit links as link rows with a tooltip', () => {
631
+ const mounted = mount({
632
+ website: 'https://example.org/a%20b',
633
+ 'support-url': 'https://example.org/help',
634
+ 'issue-url': 'https://example.org/issues',
635
+ });
636
+ mounted.dialog.developers = ['Ada <ada@example.org>', 'Bob https://example.org/bob'];
637
+ mounted.dialog.add_link('_Docs', 'https://example.org/docs');
638
+
639
+ const rows = [
640
+ ...page(mounted.dialog, 'main')!.querySelectorAll('adw-action-row.link'),
641
+ ...page(mounted.dialog, 'details')!.querySelectorAll('adw-action-row.link'),
642
+ ...page(mounted.dialog, 'credits')!.querySelectorAll('adw-action-row.link'),
643
+ ];
644
+ expect(rows.length).toBe(6);
645
+ for (const row of rows) {
646
+ expect(row.getAttribute('role')).toBe('link');
647
+ expect(row.hasAttribute('activatable')).toBe(true);
648
+ expect(row.querySelector('[icon-name="go-next"], [icon="go-next"]')).toBe(null);
649
+ expect(iconOf(row)).not.toBe(null);
650
+ }
651
+ const website = rows.find((r) => r.getAttribute('title') === label('websiteRow'))!;
652
+ expect(website.getAttribute('tooltip-text')).toBe('https://example.org/a b');
653
+ mounted.dispose();
654
+ });
655
+
656
+ await it('picks the glyph by scheme: mail-send for mailto:, external-link otherwise', () => {
657
+ const mounted = mount({ website: 'https://example.org' });
658
+ mounted.dialog.developers = ['Ada <ada@example.org>', 'Bob https://example.org/bob'];
659
+ const credits = page(mounted.dialog, 'credits')!;
660
+ const [ada, bob] = [...credits.querySelectorAll('adw-action-row.link')];
661
+ expect(iconOf(ada!)!.className).toContain('adw-icon--adw-mail-send');
662
+ expect(iconOf(bob!)!.className).toContain('adw-icon--adw-external-link');
663
+ mounted.dispose();
664
+ });
665
+
666
+ await it('keeps the chevron and no link class on navigation rows', () => {
667
+ const mounted = mount({ comments: 'Hi', website: 'https://example.org' });
668
+ mounted.dialog.developers = ['Ada'];
669
+ const nav = [...page(mounted.dialog, 'main')!.querySelectorAll('adw-action-row')].filter(
670
+ (r) => !r.classList.contains('link'),
671
+ );
672
+ expect(nav.length > 0).toBe(true);
673
+ for (const row of nav) {
674
+ expect(row.classList.contains('link')).toBe(false);
675
+ expect(row.getAttribute('role')).not.toBe('link');
676
+ expect(iconOf(row)!.className).toContain('adw-icon--go-next');
677
+ }
678
+ mounted.dispose();
679
+ });
680
+ });
440
681
  };